Create group & channel; poll prepare

This commit is contained in:
morethanwords 2020-05-09 15:02:07 +03:00
parent 1c63bf30c1
commit a884a9cea8
35 changed files with 3076 additions and 1894 deletions

View File

@ -1,161 +1,7 @@
import appSidebarRight from "../lib/appManagers/appSidebarRight";
import Scrollable from "./scrollable_new";
import appProfileManager from "../lib/appManagers/appProfileManager";
import { appPeersManager } from "../lib/services";
import appMessagesManager from "../lib/appManagers/appMessagesManager";
import appDialogsManager from "../lib/appManagers/appDialogsManager";
import appChatsManager from "../lib/appManagers/appChatsManager";
import appUsersManager from "../lib/appManagers/appUsersManager";
import { $rootScope, findUpTag, findUpClassName, cancelEvent } from "../lib/utils";
import { putPreloader } from "./misc";
class AppSelectPeers {
public container = document.createElement('div');
private chatList = document.createElement('ul');
private chatsContainer = document.createElement('div');
private scrollable: Scrollable;
private selectedScrollable: Scrollable;
private selectedContainer = document.createElement('div');
private searchInput = document.createElement('input');
private selected: {[peerID: number]: HTMLDivElement} = {};
public freezed = false;
constructor(private appendTo: HTMLDivElement, private onChange: (length: number) => void) {
this.container.classList.add('selector');
let topContainer = document.createElement('div');
topContainer.classList.add('selector-search-container');
this.selectedContainer.classList.add('selector-search');
this.searchInput.placeholder = 'Select chat';
this.searchInput.type = 'text';
this.selectedContainer.append(this.searchInput);
topContainer.append(this.selectedContainer);
this.selectedScrollable = new Scrollable(topContainer);
let delimiter = document.createElement('hr');
this.chatsContainer.classList.add('chats-container');
this.chatsContainer.append(this.chatList);
this.scrollable = new Scrollable(this.chatsContainer);
// в десктопе - сначала без группы, потом архивные, потом контакты без сообщений
let offsetIndex = 0;
appMessagesManager.getConversations(offsetIndex, 50, 0).then(value => {
let dialogs = value.dialogs;
let myID = $rootScope.myID;
offsetIndex = dialogs[value.dialogs.length - 1].index || 0;
if(dialogs[0].peerID != myID) {
dialogs.findAndSplice(d => d.peerID == myID);
dialogs.unshift({
peerID: myID,
pFlags: {}
} as any);
}
dialogs.forEach(dialog => {
let peerID = dialog.peerID;
let {dom} = appDialogsManager.addDialog(dialog, this.chatList, false, false);
dom.containerEl.insertAdjacentHTML('afterbegin', '<div class="checkbox"><label><input type="checkbox"><span></span></label></div>');
let subtitle = '';
if(peerID < 0) {
subtitle = appChatsManager.getChatMembersString(-peerID);
} else if(peerID == myID) {
subtitle = 'chat with yourself';
} else {
subtitle = appUsersManager.getUserStatusString(peerID);
if(subtitle == 'online') {
subtitle = `<i>${subtitle}</i>`;
}
}
dom.lastMessageSpan.innerHTML = subtitle;
});
});
this.chatList.addEventListener('click', (e) => {
let target = e.target as HTMLElement;
cancelEvent(e);
if(this.freezed) return;
if(target.tagName != 'LI') {
target = findUpTag(target, 'LI');
}
if(!target) return;
let peerID = +target.getAttribute('data-peerID');
target.classList.toggle('active');
if(peerID in this.selected) {
this.remove(peerID);
} else {
this.add(peerID);
}
let checkbox = target.querySelector('input') as HTMLInputElement;
checkbox.checked = !checkbox.checked;
});
this.selectedContainer.addEventListener('click', (e) => {
if(this.freezed) return;
let target = e.target as HTMLElement;
target = findUpClassName(target, 'selector-user');
if(!target) return;
let peerID = target.dataset.peerID;
let li = this.chatList.querySelector('[data-peerid="' + peerID + '"]') as HTMLElement;
li.click();
});
this.container.append(topContainer, delimiter, this.chatsContainer);
appendTo.append(this.container);
}
private add(peerID: number) {
let div = document.createElement('div');
div.classList.add('selector-user', 'scale-in');
div.dataset.peerID = '' + peerID;
this.selected[peerID] = div;
let title = appPeersManager.getPeerTitle(peerID, false, true);
let avatarDiv = document.createElement('div');
avatarDiv.classList.add('user-avatar', 'tgico');
appProfileManager.putPhoto(avatarDiv, peerID);
div.innerHTML = title;
div.insertAdjacentElement('afterbegin', avatarDiv);
this.selectedContainer.insertBefore(div, this.searchInput);
this.selectedScrollable.scrollTop = this.selectedScrollable.scrollHeight;
this.onChange(Object.keys(this.selected).length);
}
private remove(peerID: number) {
let div = this.selected[peerID];
div.classList.remove('scale-in');
void div.offsetWidth;
div.classList.add('scale-out');
div.addEventListener('animationend', () => {
delete this.selected[peerID];
div.remove();
this.onChange(Object.keys(this.selected).length);
}, {once: true});
}
public getSelected() {
return Object.keys(this.selected).map(p => +p);
}
}
import { AppSelectPeers } from "./appSelectPeers";
class AppForward {
private container = document.getElementById('forward-container') as HTMLDivElement;

View File

@ -13,10 +13,10 @@ export class SearchGroup {
nameEl: HTMLDivElement;
list: HTMLUListElement;
constructor(public name: string, public type: string, private clearable = true, className?: string) {
constructor(public name: string, public type: string, private clearable = true, className?: string, clickable = true) {
this.list = document.createElement('ul');
this.container = document.createElement('div');
if(className) this.container.classList.add(className);
if(className) this.container.className = className;
this.nameEl = document.createElement('div');
this.nameEl.classList.add('search-group__name');
this.nameEl.innerText = name;
@ -25,7 +25,9 @@ export class SearchGroup {
this.container.append(this.nameEl, this.list);
this.container.style.display = 'none';
appDialogsManager.setListClickListener(this.list);
if(clickable) {
appDialogsManager.setListClickListener(this.list);
}
}
clear() {

View File

@ -0,0 +1,214 @@
import Scrollable from "./scrollable_new";
import appMessagesManager from "../lib/appManagers/appMessagesManager";
import { $rootScope, cancelEvent, findUpTag, findUpClassName } from "../lib/utils";
import appDialogsManager from "../lib/appManagers/appDialogsManager";
import appChatsManager from "../lib/appManagers/appChatsManager";
import appUsersManager from "../lib/appManagers/appUsersManager";
import { appPeersManager } from "../lib/services";
import appProfileManager from "../lib/appManagers/appProfileManager";
import appPhotosManager from "../lib/appManagers/appPhotosManager";
export class AppSelectPeers {
public container = document.createElement('div');
private list = document.createElement('ul');
private chatsContainer = document.createElement('div');
private scrollable: Scrollable;
private selectedScrollable: Scrollable;
private selectedContainer = document.createElement('div');
private input = document.createElement('input');
private selected: {[peerID: number]: HTMLDivElement} = {};
public freezed = false;
private myID = $rootScope.myID;
private offsetIndex = 0;
private promise: Promise<number[]>;
private query = '';
private cachedContacts: number[];
constructor(private appendTo: HTMLDivElement, private onChange?: (length: number) => void, private peerType: 'contacts' | 'dialogs' = 'dialogs') {
this.container.classList.add('selector');
let topContainer = document.createElement('div');
topContainer.classList.add('selector-search-container');
this.selectedContainer.classList.add('selector-search');
this.input.placeholder = peerType == 'contacts' ? 'Add People...' : 'Select chat';
this.input.type = 'text';
this.selectedContainer.append(this.input);
topContainer.append(this.selectedContainer);
this.selectedScrollable = new Scrollable(topContainer);
let delimiter = document.createElement('hr');
this.chatsContainer.classList.add('chats-container');
this.chatsContainer.append(this.list);
this.scrollable = new Scrollable(this.chatsContainer);
this.list.addEventListener('click', (e) => {
let target = e.target as HTMLElement;
cancelEvent(e);
if(this.freezed) return;
if(target.tagName != 'LI') {
target = findUpTag(target, 'LI');
}
if(!target) return;
let peerID = +target.getAttribute('data-peerID');
target.classList.toggle('active');
if(peerID in this.selected) {
this.remove(peerID);
} else {
this.add(peerID);
}
let checkbox = target.querySelector('input') as HTMLInputElement;
checkbox.checked = !checkbox.checked;
});
this.selectedContainer.addEventListener('click', (e) => {
if(this.freezed) return;
let target = e.target as HTMLElement;
target = findUpClassName(target, 'selector-user');
if(!target) return;
let peerID = target.dataset.peerID;
let li = this.list.querySelector('[data-peerid="' + peerID + '"]') as HTMLElement;
li.click();
});
this.input.addEventListener('input', () => {
let value = this.input.value;
if(this.query != value) {
if(this.peerType == 'contacts') {
this.cachedContacts = null;
this.promise = null;
}
this.list.innerHTML = '';
this.query = value;
console.log('selectPeers input:', this.query);
this.getMoreResults();
}
});
this.scrollable.onScrolledBottom = () => {
this.getMoreResults();
};
this.container.append(topContainer, delimiter, this.chatsContainer);
appendTo.append(this.container);
this.getMoreResults();
}
private getMoreDialogs() {
// в десктопе - сначала без группы, потом архивные, потом контакты без сообщений
appMessagesManager.getConversations(this.offsetIndex, 50, 0).then(value => {
let dialogs = value.dialogs;
this.offsetIndex = dialogs[value.dialogs.length - 1].index || 0;
if(dialogs[0].peerID != this.myID) {
dialogs.findAndSplice(d => d.peerID == this.myID);
dialogs.unshift({
peerID: this.myID,
pFlags: {}
} as any);
}
this.renderResults(dialogs.map(dialog => dialog.peerID));
});
}
private async getMoreContacts() {
if(this.promise) return this.promise;
if(!this.cachedContacts) {
this.promise = appUsersManager.getContacts(this.query);
this.cachedContacts = (await this.promise).slice();
this.cachedContacts.findAndSplice(userID => userID == this.myID); // no my account
this.promise = null;
}
if(this.cachedContacts.length) {
let pageCount = appPhotosManager.windowH / 72 * 1.25 | 0;
let arr = this.cachedContacts.splice(0, pageCount);
this.renderResults(arr);
}
}
private getMoreResults() {
if(this.peerType == 'dialogs') {
this.getMoreDialogs();
} else {
this.getMoreContacts();
}
}
private renderResults(peerIDs: number[]) {
peerIDs.forEach(peerID => {
let {dom} = appDialogsManager.addDialog(peerID, this.list, false, false);
dom.containerEl.insertAdjacentHTML('afterbegin', '<div class="checkbox"><label><input type="checkbox"><span></span></label></div>');
let subtitle = '';
if(peerID < 0) {
subtitle = appChatsManager.getChatMembersString(-peerID);
} else if(peerID == this.myID) {
subtitle = 'chat with yourself';
} else {
subtitle = appUsersManager.getUserStatusString(peerID);
if(subtitle == 'online') {
subtitle = `<i>${subtitle}</i>`;
}
}
dom.lastMessageSpan.innerHTML = subtitle;
});
}
private add(peerID: number) {
let div = document.createElement('div');
div.classList.add('selector-user', 'scale-in');
div.dataset.peerID = '' + peerID;
this.selected[peerID] = div;
let title = appPeersManager.getPeerTitle(peerID, false, true);
let avatarDiv = document.createElement('div');
avatarDiv.classList.add('user-avatar', 'tgico');
appProfileManager.putPhoto(avatarDiv, peerID);
div.innerHTML = title;
div.insertAdjacentElement('afterbegin', avatarDiv);
this.selectedContainer.insertBefore(div, this.input);
this.selectedScrollable.scrollTop = this.selectedScrollable.scrollHeight;
this.onChange && this.onChange(Object.keys(this.selected).length);
}
private remove(peerID: number) {
let div = this.selected[peerID];
div.classList.remove('scale-in');
void div.offsetWidth;
div.classList.add('scale-out');
div.addEventListener('animationend', () => {
delete this.selected[peerID];
div.remove();
this.onChange && this.onChange(Object.keys(this.selected).length);
}, {once: true});
}
public getSelected() {
return Object.keys(this.selected).map(p => +p);
}
}

200
src/components/poll.ts Normal file
View File

@ -0,0 +1,200 @@
import appPollsManager, { PollResults } from "../lib/appManagers/appPollsManager";
import { RichTextProcessor } from "../lib/richtextprocessor";
let lineTotalLength = 0;
const tailLength = 9;
const times = 10;
const fullTime = 340;
const oneTime = fullTime / times;
export default class PollElement extends HTMLElement {
private svgLines: SVGSVGElement[];
private numberDivs: HTMLDivElement[];
private maxOffset = -44.8;
private maxLength: number;
private maxLengths: number[];
constructor() {
super();
// элемент создан
}
connectedCallback() {
// браузер вызывает этот метод при добавлении элемента в документ
// (может вызываться много раз, если элемент многократно добавляется/удаляется)
if(!lineTotalLength) {
lineTotalLength = (document.getElementById('poll-line') as any as SVGPathElement).getTotalLength();
console.log('line total length:', lineTotalLength);
}
let pollID = this.getAttribute('poll-id');
let {poll, results} = appPollsManager.getPoll(pollID);
console.log('pollElement poll:', poll, results);
let desc = '';
if(poll.pFlags) {
if(poll.pFlags.closed) {
desc = 'Final results';
} else {
desc = poll.pFlags.public_voters ? 'Public Poll' : 'Anonymous Poll';
}
}
let votes = poll.answers.map(answer => {
return `
<div class="poll-answer">
<div class="circle-hover">
<div class="animation-ring"></div>
<svg class="progress-ring">
<circle class="progress-ring__circle" cx="13" cy="13" r="9"></circle>
</svg>
</div>
<div class="poll-answer-percents"></div>
<svg version="1.1" class="poll-line" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 480 28" xml:space="preserve">
<use href="#poll-line"></use>
</svg>
<div class="poll-answer-text">${RichTextProcessor.wrapEmojiText(answer.text)}</div>
</div>
`;
}).join('');
this.innerHTML = `
<div class="poll-title">${poll.rQuestion}</div>
<div class="poll-desc">${desc}</div>
${votes}
<div class="poll-votes-count">${results.total_voters ? results.total_voters + ' voters' : 'No votes'}</div>
`;
let width = this.getBoundingClientRect().width;
this.maxLength = width + tailLength + this.maxOffset + -9; // 13 - position left
this.performResults(results);
}
disconnectedCallback() {
// браузер вызывает этот метод при удалении элемента из документа
// (может вызываться много раз, если элемент многократно добавляется/удаляется)
}
static get observedAttributes(): string[] {
return [/* массив имён атрибутов для отслеживания их изменений */];
}
attributeChangedCallback(name: string, oldValue: string, newValue: string) {
// вызывается при изменении одного из перечисленных выше атрибутов
}
adoptedCallback() {
// вызывается, когда элемент перемещается в новый документ
// (происходит в document.adoptNode, используется очень редко)
}
performResults(results: PollResults) {
const percents = results.results.map(v => v.voters / results.total_voters * 100);
this.setResults(percents);
}
setResults(percents: number[]) {
if(!this.svgLines) {
this.svgLines = Array.from(this.querySelectorAll('.poll-line')) as SVGSVGElement[];
this.numberDivs = Array.from(this.querySelectorAll('.poll-answer-percents')) as HTMLDivElement[];
}
let maxValue = Math.max(...percents);
this.maxLengths = percents.map(p => p / maxValue * this.maxLength);
/* this.svgLines.forEach((svg, idx) => {
this.setLineProgress(idx, 1);
}); */
/* percents = percents.map(p => {
return Math.round(p);
}); */
console.log('setResults before percents:', percents);
let sum = percents.reduce((acc, p) => acc + Math.round(p), 0);
if(sum > 100) {
let diff = sum - 100;
let length = percents.length;
for(let i = 0; i < diff; ++i) {
let minIndex = -1, minRemainder = 1;
for(let k = 0; k < length; ++k) {
let remainder = percents[k] % 1;
if(remainder >= 0.5 && remainder < minRemainder) {
minRemainder = remainder;
minIndex = k;
}
}
if(minIndex == -1) {
throw new Error('lol chto');
}
percents[minIndex] -= minRemainder;
}
} else {
let diff = 100 - sum;
let length = percents.length;
for(let i = 0; i < diff; ++i) {
let minIndex = -1, maxRemainder = 0;
for(let k = 0; k < length; ++k) {
let remainder = percents[k] % 1;
if(remainder < 0.5 && remainder > maxRemainder) {
maxRemainder = remainder;
minIndex = k;
}
}
if(minIndex == -1) {
throw new Error('lol chto');
}
percents[minIndex] += 1 - maxRemainder;
}
}
console.log('setResults after percents:', percents, sum);
let start = Date.now();
let r = () => {
let diff = Date.now() - start;
let progress = diff / fullTime;
if(progress > 1) progress = 1;
this.svgLines.forEach((svg, idx) => {
this.setLineProgress(idx, progress);
});
if(progress < 1) {
window.requestAnimationFrame(r);
}
};
window.requestAnimationFrame(r);
for(let i = 0; i < times; ++i) {
setTimeout(() => {
percents.forEach((percents, idx) => {
let value = Math.round(percents / times * (i + 1));
let div = this.numberDivs[idx];
//div.style.opacity = ((i + 1) * 0.10).toFixed(1); // опасити в 10 шагов от 0.1 до 1
div.innerText = value + '%';
});
}, oneTime * i);
}
this.classList.add('is-voted');
}
setLineProgress(index: number, percents: number) {
let svg = this.svgLines[index];
svg.style.strokeDasharray = (percents * this.maxLengths[index]) + ', 485.9';
svg.style.strokeDashoffset = '' + percents * this.maxOffset;
}
// у элемента могут быть ещё другие методы и свойства
}
customElements.define("poll-element", PollElement);

View File

@ -0,0 +1,97 @@
import resizeableImage from "../lib/cropper";
import apiFileManager from "../lib/mtproto/apiFileManager";
export class PopupAvatar {
private container = document.getElementById('popup-avatar');
private input = this.container.querySelector('input') as HTMLInputElement;
private cropContainer = this.container.querySelector('.crop') as HTMLDivElement;
private closeBtn = this.container.querySelector('.popup-close') as HTMLButtonElement;
private image = new Image();
private canvas: HTMLCanvasElement;
private blob: Blob;
private cropper = {
crop: () => {},
removeHandlers: () => {}
};
private onCrop: (upload: () => Promise<any>) => void;
constructor() {
this.container.style.display = ''; // need for no blink
this.cropContainer.append(this.image);
this.input.addEventListener('change', (e: any) => {
var file = e.target.files[0];
if(!file) {
return;
}
var reader = new FileReader();
reader.onload = (e) => {
var contents = e.target.result as string;
this.image = new Image();
this.cropContainer.append(this.image);
this.image.src = contents;
this.image.onload = () => {
/* let {w, h} = calcImageInBox(this.image.naturalWidth, this.image.naturalHeight, 460, 554);
cropContainer.style.width = w + 'px';
cropContainer.style.height = h + 'px'; */
this.container.classList.remove('hide');
void this.container.offsetWidth; // reflow
this.container.classList.add('active');
this.cropper = resizeableImage(this.image, this.canvas);
this.input.value = '';
};
};
reader.readAsDataURL(file);
}, false);
// apply
this.container.querySelector('.btn-crop').addEventListener('click', () => {
this.cropper.crop();
this.closeBtn.click();
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.resolve();
}, 'image/jpeg', 1);
});
this.closeBtn.addEventListener('click', () => {
setTimeout(() => {
this.cropper.removeHandlers();
if(this.image) {
this.image.remove();
}
this.container.classList.add('hide');
}, 200);
});
}
private resolve() {
this.onCrop(() => {
return apiFileManager.uploadFile(this.blob);
});
}
public open(postCanvas: HTMLCanvasElement, onCrop: (upload: () => Promise<any>) => void) {
this.canvas = postCanvas;
this.onCrop = onCrop;
this.input.click();
}
}
export default new PopupAvatar();

View File

@ -15,6 +15,8 @@ import { CancellablePromise } from '../lib/polyfill';
import { renderImageFromUrl } from './misc';
import appMessagesManager from '../lib/appManagers/appMessagesManager';
import { Layouter, RectPart } from './groupedLayout';
import { Poll, PollResults } from '../lib/appManagers/appPollsManager';
import PollElement from './poll';
export type MTDocument = {
_: 'document' | 'documentEmpty',
@ -701,11 +703,11 @@ export function wrapSticker(doc: MTDocument, div: HTMLDivElement, middleware?: (
}
if(!stickerType) {
console.error('wrong doc for wrapSticker!', doc, div);
console.error('wrong doc for wrapSticker!', doc);
return Promise.resolve();
}
console.log('wrap sticker', doc, div, onlyThumb);
//console.log('wrap sticker', doc, div, onlyThumb);
if(doc.thumbs && !div.firstElementChild && (!doc.downloaded || stickerType == 2)) {
let thumb = doc.thumbs[0];
@ -875,6 +877,10 @@ export function wrapReply(title: string, subtitle: string, message?: any) {
if(media) {
if(message.grouped_id) {
replySubtitle.innerHTML = 'Album';
} else if(media._ == 'messageMediaContact') {
replySubtitle.innerHTML = 'Contact';
} else if(media._ == 'messageMediaPoll') {
replySubtitle.innerHTML = media.poll.rReply;
} else if(media.photo) {
replySubtitle.innerHTML = 'Photo';
} else if(media.document && media.document.type) {
@ -946,7 +952,13 @@ export function wrapAlbum({groupID, attachmentDiv, middleware, uploading, lazyLo
let brSplitted = fillPropertyValue(borderRadius); */
for(let {geometry, sides} of layout) {
let {size, media, message} = items.shift();
let item = items.shift();
if(!item) {
console.error('no item for layout!');
continue;
}
let {size, media, message} = item;
let div = document.createElement('div');
div.classList.add('album-item');
div.dataset.mid = message.mid;
@ -1041,3 +1053,9 @@ export function wrapAlbum({groupID, attachmentDiv, middleware, uploading, lazyLo
attachmentDiv.append(div);
}
}
export function wrapPoll(poll: Poll, results: PollResults) {
let elem = new PollElement();
elem.setAttribute('poll-id', poll.id);
return elem;
}

68
src/format_schema.js Normal file
View File

@ -0,0 +1,68 @@
let json = require('./schema');
let top = {};
/* ['MTProto', 'API'].forEach(key => {
let schema = json[key];
let out = {constructors: {}, methods: {}};
['constructors', 'methods'].forEach(key => {
schema[key].forEach(smth => {
let id = smth.id;
if(id < 0) {
id = +id + 4294967296;
}
out[key][id] = smth;
delete smth.id;
});
});
top[key] = out;
//console.log(out);
//process.exit(0);
}); */
['MTProto', 'API'].forEach(key => {
let schema = json[key];
['constructors', 'methods'].forEach(key => {
schema[key].forEach(smth => {
if(+smth.id < 0) {
smth.id = +smth.id + 4294967296;
}
});
});
//console.log(out);
//process.exit(0);
});
top = json;
/* ['API'].forEach(key => {
let schema = json[key];
let out = {constructors: {}, methods: {}};
['constructors', 'methods'].forEach(key => {
schema[key].forEach(smth => {
let id = smth.id;
if(id < 0) {
id = id + 4294967296;
}
out[key][id] = smth;
delete smth.id;
});
});
top[key] = out;
//console.log(out);
//process.exit(0);
}); */
//console.log(out);
require('fs').writeFileSync('./schema_pretty.json', JSON.stringify(top/* , null, '\t' */));

View File

@ -1,9 +1,43 @@
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";
type Channel = {
_: 'channel',
flags: number,
pFlags: Partial<{
creator: true,
left: true,
broadcast: true,
verified: true,
megagroup: true,
restricted: true,
signatures: true,
min: true,
scam: true,
has_link: true,
has_geo: true,
slowmode_enabled: true
}>,
id: number,
access_hash?: string,
title: string,
username?: string,
photo: any,
date: number,
version: number,
restriction_reason?: any,
admin_rights?: any,
banned_rights?: any,
default_banned_rights?: any,
participants_count: number
};
export class AppChatsManager {
public chats: any = {};
public chats: {[id: number]: Channel | any} = {};
public usernames: any = {};
public channelAccess: any = {};
public megagroups: any = {};
@ -143,8 +177,7 @@ export class AppChatsManager {
public isChannel(id: number) {
var chat = this.chats[id];
if(chat && (chat._ == 'channel' || chat._ == 'channelForbidden') ||
this.channelAccess[id]) {
if(chat && (chat._ == 'channel' || chat._ == 'channelForbidden') || this.channelAccess[id]) {
return true;
}
return false;
@ -166,10 +199,6 @@ export class AppChatsManager {
return this.isChannel(id) && !this.isMegagroup(id);
}
public getChatInput(id: number) {
return id || 0;
}
public getChannelInput(id: number) {
if(!id) {
return {_: 'inputChannelEmpty'};
@ -182,6 +211,25 @@ export class AppChatsManager {
};
}
public getChatInputPeer(id: number) {
return {
_: 'inputPeerChat',
chat_id: id
};
}
public getChannelInputPeer(id: number) {
if(!id) {
return {_: 'inputPeerEmpty'};
}
return {
_: 'inputPeerChannel',
channel_id: id,
access_hash: this.getChat(id).access_hash || this.channelAccess[id] || 0
};
}
public hasChat(id: number, allowMin?: any) {
var chat = this.chats[id]
return isObject(chat) && (allowMin || !chat.pFlags.min);
@ -217,7 +265,7 @@ export class AppChatsManager {
var chatFull = copy(fullChat);
var chat = this.getChat(id);
if (!chatFull.participants_count) {
if(!chatFull.participants_count) {
chatFull.participants_count = chat.participants_count;
}
@ -226,7 +274,7 @@ export class AppChatsManager {
chatFull.participants.participants = this.wrapParticipants(id, chatFull.participants.participants);
}
if (chatFull.about) {
if(chatFull.about) {
chatFull.rAbout = RichTextProcessor.wrapRichText(chatFull.about, {noLinebreaks: true});
}
@ -240,7 +288,7 @@ export class AppChatsManager {
var chat = this.getChat(id);
var myID = appUsersManager.getSelf().id;
if(this.isChannel(id)) {
var isAdmin = chat.pFlags.creator || chat.pFlags.editor || chat.pFlags.moderator
var isAdmin = chat.pFlags.creator || chat.pFlags.editor || chat.pFlags.moderator;
participants.forEach((participant) => {
participant.canLeave = myID == participant.user_id;
participant.canKick = isAdmin && participant._ == 'channelParticipant';
@ -249,7 +297,7 @@ export class AppChatsManager {
participant.user = appUsersManager.getUser(participant.user_id);
});
} else {
var isAdmin = chat.pFlags.creator || chat.pFlags.admins_enabled && chat.pFlags.admin
var isAdmin = chat.pFlags.creator || chat.pFlags.admins_enabled && chat.pFlags.admin;
participants.forEach((participant) => {
participant.canLeave = myID == participant.user_id;
participant.canKick = !participant.canLeave && (
@ -261,8 +309,70 @@ export class AppChatsManager {
participant.user = appUsersManager.getUser(participant.user_id);
});
}
return participants;
}
public createChannel(title: string, about: String): Promise<number> {
return apiManager.invokeApi('channels.createChannel', {
flags: 1,
broadcast: true,
title: title,
about: about
}).then((updates: any) => {
apiUpdatesManager.processUpdateMessage(updates);
return updates.chats[0].id;
});
}
public inviteToChannel(id: number, userIDs: number[]) {
let input = this.getChannelInput(id);
let usersInputs = userIDs.map(u => appUsersManager.getUserInput(u));
return apiManager.invokeApi('channels.inviteToChannel', {
channel: input,
users: usersInputs
}).then(updates => {
apiUpdatesManager.processUpdateMessage(updates);
});
}
public createChat(title: string, userIDs: number[]): Promise<number> {
return apiManager.invokeApi('messages.createChat', {
users: userIDs.map(u => appUsersManager.getUserInput(u)),
title: title
}).then(updates => {
apiUpdatesManager.processUpdateMessage(updates);
return updates.chats[0].id;
});
}
public editPhoto(id: number, inputFile: any) {
let isChannel = this.isChannel(id);
let inputChatPhoto = {
_: 'inputChatUploadedPhoto',
file: inputFile
};
if(isChannel) {
return apiManager.invokeApi('channels.editPhoto', {
channel: this.getChannelInputPeer(id),
photo: inputChatPhoto
}).then(updates => {
apiUpdatesManager.processUpdateMessage(updates);
});
} else {
return apiManager.invokeApi('messages.editChatPhoto', {
chat_id: id,
photo: inputChatPhoto
}).then(updates => {
apiUpdatesManager.processUpdateMessage(updates);
});
}
}
}
export default new AppChatsManager();

View File

@ -9,6 +9,7 @@ import { ripple, putPreloader } from "../../components/misc";
import Scrollable from "../../components/scrollable_new";
import appProfileManager from "./appProfileManager";
import { logger } from "../polyfill";
import appChatsManager from "./appChatsManager";
type DialogDom = {
avatarDiv: HTMLDivElement,
@ -474,6 +475,12 @@ export class AppDialogsManager {
case 'messageMediaGeo':
lastMessageText += '<i>Geolocation</i>';
break;
case 'messageMediaPoll':
lastMessageText += '<i>' + lastMessage.media.poll.rReply + '</i>';
break;
case 'messageMediaContact':
lastMessageText += '<i>Contact</i>';
break;
case 'messageMediaDocument':
let document = lastMessage.media.document;
@ -724,6 +731,18 @@ export class AppDialogsManager {
let titleSpan = document.createElement('span');
titleSpan.classList.add('user-title');
if(peerID < 0) {
let chat = appChatsManager.getChat(-peerID);
if(chat && chat.pFlags && chat.pFlags.verified) {
titleSpan.classList.add('is-verified');
}
} else {
let user = appUsersManager.getUser(peerID);
if(user && user.pFlags && user.pFlags.verified) {
titleSpan.classList.add('is-verified');
}
}
if(peerID == $rootScope.myID) {
title = onlyFirstName ? 'Saved' : 'Saved Messages';
}

View File

@ -1,6 +1,6 @@
//import apiManager from '../mtproto/apiManager';
import apiManager from '../mtproto/mtprotoworker';
import { $rootScope, isElementInViewport, numberWithCommas, findUpClassName, formatNumber, placeCaretAtEnd, findUpTag, langPack } from "../utils";
import { $rootScope, numberWithCommas, findUpClassName, formatNumber, placeCaretAtEnd, findUpTag, langPack } from "../utils";
import appUsersManager from "./appUsersManager";
import appMessagesManager from "./appMessagesManager";
import appPeersManager from "./appPeersManager";
@ -17,7 +17,7 @@ import appSidebarLeft from "./appSidebarLeft";
import appChatsManager from "./appChatsManager";
import appMessagesIDsManager from "./appMessagesIDsManager";
import apiUpdatesManager from './apiUpdatesManager';
import { wrapDocument, wrapPhoto, wrapVideo, wrapSticker, wrapReply, wrapAlbum } from '../../components/wrappers';
import { wrapDocument, wrapPhoto, wrapVideo, wrapSticker, wrapReply, wrapAlbum, wrapPoll } from '../../components/wrappers';
import ProgressivePreloader from '../../components/preloader';
import { openBtnMenu, formatPhoneNumber } from '../../components/misc';
import { ChatInput } from '../../components/chatInput';
@ -838,8 +838,21 @@ export class AppImManager {
let [chatOnlines, chatInfo] = results;
let onlines = chatOnlines ? chatOnlines.onlines : 1;
if(chatInfo._ == 'chatFull' && chatInfo.participants && chatInfo.participants.participants) {
let participants = chatInfo.participants.participants;
onlines = participants.reduce((acc: number, participant: any) => {
let user = appUsersManager.getUser(participant.user_id);
if(user && user.status && user.status._ == 'userStatusOnline') {
return acc + 1;
}
return acc;
}, 0);
}
///////////this.log('chatInfo res:', chatInfo);
this.log('chatInfo res:', chatInfo);
if(chatInfo.pinned_msg_id) { // request pinned message
this.pinnedMsgID = chatInfo.pinned_msg_id;
@ -882,6 +895,9 @@ export class AppImManager {
}
}
}
} else {
this.subtitleEl.innerText = 'bot';
appSidebarRight.profileElements.subtitle.innerText = 'bot';
}
}
@ -1058,7 +1074,7 @@ export class AppImManager {
this.scroll.scrollTop = this.scroll.scrollHeight;
}
}
/* this.onScroll();
this.scrollable.onScroll();*/
@ -1225,7 +1241,8 @@ export class AppImManager {
// reverse means top
public renderMessage(message: any, reverse = false, multipleRender = false, bubble: HTMLDivElement = null, updatePosition = true) {
//this.log('message to render:', message);
this.log('message to render:', message);
//return;
if(message.deleted) return;
else if(message.grouped_id) { // will render only last album's message
let storage = appMessagesManager.groupedMessagesStorage[message.grouped_id];
@ -1278,8 +1295,14 @@ export class AppImManager {
if(_ == "messageActionPhoneCall") {
_ += '.' + action.type;
}
// @ts-ignore
let str = (name.innerText ? name.outerHTML + ' ' : '') + langPack[_];
let l = langPack[_];
if(!l) {
l = '[' + _ + ']';
}
let str = l[0].toUpperCase() == l[0] ? l : (name.innerText ? name.outerHTML + ' ' : '') + l;
bubbleContainer.innerHTML = `<div class="service-msg">${str}</div>`;
/* if(!updatePosition) {
@ -1401,7 +1424,7 @@ export class AppImManager {
}
// media
if(message.media) {
if(message.media/* && message.media._ == 'messageMediaPhoto' */) {
let attachmentDiv = document.createElement('div');
attachmentDiv.classList.add('attachment');
@ -1712,6 +1735,15 @@ export class AppImManager {
break;
}
case 'messageMediaPoll': {
bubble.classList.remove('is-message-empty');
let pollElement = wrapPoll(message.media.poll, message.media.results);
messageDiv.prepend(pollElement);
break;
}
default:
bubble.classList.remove('is-message-empty');
@ -1877,7 +1909,7 @@ export class AppImManager {
let _history = history.slice();
setTimeout(() => {
this.performHistoryResult(_history, reverse, isBackLimit, 0, resetPromises);
}, (i + 1) * 2500);
}, 0/* (i + 1) * 2500 */);
}
}
@ -2092,8 +2124,8 @@ export class AppImManager {
this.log('getHistory: slice loadedTimes:', reverse, pageCount, this.loadedTopTimes, this.loadedBottomTimes, ids && ids.length);
let removeCount = loadCount / 2;
let safeCount = realLoadCount * 2;
//let removeCount = loadCount / 2;
let safeCount = Math.min(realLoadCount * 2, 35); // cause i've been runningrunningrunning all day
if(ids && ids.length > safeCount) {
if(reverse) {
//ids = ids.slice(-removeCount);
@ -2210,49 +2242,51 @@ export class AppImManager {
public handleUpdate(update: any) {
switch(update._) {
case 'updateUserTyping':
case 'updateChatUserTyping':
if(this.myID == update.user_id) {
return;
}
var peerID = update._ == 'updateUserTyping' ? update.user_id : -update.chat_id;
this.typingUsers[update.user_id] = peerID;
if(!appUsersManager.hasUser(update.user_id)) {
if(update.chat_id && appChatsManager.hasChat(update.chat_id) && !appChatsManager.isChannel(update.chat_id)) {
appProfileManager.getChatFull(update.chat_id);
case 'updateChatUserTyping': {
if(this.myID == update.user_id) {
return;
}
//return;
}
appUsersManager.forceUserOnline(update.user_id);
let dialog = appMessagesManager.getDialogByPeerID(peerID)[0];
let currentPeer = this.peerID == peerID;
if(this.typingTimeouts[peerID]) clearTimeout(this.typingTimeouts[peerID]);
else if(dialog) {
appDialogsManager.setTyping(dialog, appUsersManager.getUser(update.user_id));
var peerID = update._ == 'updateUserTyping' ? update.user_id : -update.chat_id;
this.typingUsers[update.user_id] = peerID;
if(currentPeer) { // user
if(!appUsersManager.hasUser(update.user_id)) {
if(update.chat_id && appChatsManager.hasChat(update.chat_id) && !appChatsManager.isChannel(update.chat_id)) {
appProfileManager.getChatFull(update.chat_id);
}
//return;
}
appUsersManager.forceUserOnline(update.user_id);
let dialog = appMessagesManager.getDialogByPeerID(peerID)[0];
let currentPeer = this.peerID == peerID;
if(this.typingTimeouts[peerID]) clearTimeout(this.typingTimeouts[peerID]);
else if(dialog) {
appDialogsManager.setTyping(dialog, appUsersManager.getUser(update.user_id));
if(currentPeer) { // user
this.setPeerStatus();
}
}
this.typingTimeouts[peerID] = setTimeout(() => {
this.typingTimeouts[peerID] = 0;
delete this.typingUsers[update.user_id];
if(dialog) {
appDialogsManager.unsetTyping(dialog);
}
// лень просчитывать случаи
this.setPeerStatus();
}
}, 6000);
break;
}
this.typingTimeouts[peerID] = setTimeout(() => {
this.typingTimeouts[peerID] = 0;
delete this.typingUsers[update.user_id];
if(dialog) {
appDialogsManager.unsetTyping(dialog);
}
// лень просчитывать случаи
this.setPeerStatus();
}, 6000);
break;
case 'updateNotifySettings': {
let {peer, notify_settings} = update;

View File

@ -21,6 +21,7 @@ import serverTimeManager from "../mtproto/serverTimeManager";
import apiManager from '../mtproto/mtprotoworker';
import appWebPagesManager from "./appWebPagesManager";
import { CancellablePromise, deferredPromise } from "../polyfill";
import appPollsManager from "./appPollsManager";
const APITIMEOUT = 0;
@ -1501,7 +1502,10 @@ export class AppMessagesManager {
apiMessage.media.photo = appPhotosManager.savePhoto(apiMessage.media.photo, mediaContext);
//appPhotosManager.savePhoto(apiMessage.media.photo, mediaContext);
}
break
break;
case 'messageMediaPoll':
appPollsManager.savePoll(apiMessage.media.poll, apiMessage.media.results);
break;
case 'messageMediaDocument':
if(apiMessage.media.ttl_seconds) {
apiMessage.media = {_: 'messageMediaUnsupportedWeb'};

View File

@ -30,10 +30,12 @@ const AppPeersManager = {
if(peerID >= 0) {
return false;
}
var chat = appChatsManager.getChat(-peerID);
let chat = appChatsManager.getChat(-peerID);
if(chat && chat.migrated_to && chat.pFlags.deactivated) {
return AppPeersManager.getPeerID(chat.migrated_to);
}
return false;
},
@ -66,10 +68,11 @@ const AppPeersManager = {
return {_: 'peerUser', user_id: peerID};
}
var chatID = -peerID;
let chatID = -peerID;
if(appChatsManager.isChannel(chatID)) {
return {_: 'peerChannel', channel_id: chatID};
}
return {_: 'peerChat', chat_id: chatID};
},
@ -99,8 +102,8 @@ const AppPeersManager = {
? peerString.user_id
: -(peerString.channel_id || peerString.chat_id);
}
var isUser = peerString.charAt(0) == 'u';
var peerParams = peerString.substr(1).split('_');
let isUser = peerString.charAt(0) == 'u';
let peerParams = peerString.substr(1).split('_');
return isUser ? peerParams[0] : -peerParams[0] || 0;
},
@ -125,25 +128,52 @@ const AppPeersManager = {
return (peerID > 0) && appUsersManager.isBot(peerID);
},
getInputPeerByID: (peerID: number) => {
if (!peerID) {
return {_: 'inputPeerEmpty'}
getInputPeer: (peerString: string): any => {
var firstChar = peerString.charAt(0);
var peerParams = peerString.substr(1).split('_');
let id = +peerParams[0];
if(firstChar == 'u') {
appUsersManager.saveUserAccess(id, peerParams[1]);
return {
_: 'inputPeerUser',
user_id: id,
access_hash: peerParams[1]
};
} else if(firstChar == 'c' || firstChar == 's') {
appChatsManager.saveChannelAccess(id, peerParams[1]);
if(firstChar == 's') {
appChatsManager.saveIsMegagroup(id);
}
return {
_: 'inputPeerChannel',
channel_id: id,
access_hash: peerParams[1] || 0
};
} else {
return {
_: 'inputPeerChat',
chat_id: id
};
}
if (peerID < 0) {
var chatID = -peerID
if (!appChatsManager.isChannel(chatID)) {
return {
_: 'inputPeerChat',
chat_id: chatID
};
},
getInputPeerByID: (peerID: number) => {
if(!peerID) {
return {_: 'inputPeerEmpty'};
}
if(peerID < 0) {
let chatID = -peerID;
if(!appChatsManager.isChannel(chatID)) {
return appChatsManager.getChatInputPeer(chatID);
} else {
return {
_: 'inputPeerChannel',
channel_id: chatID,
access_hash: appChatsManager.getChat(chatID).access_hash || 0
};
return appChatsManager.getChannelInputPeer(chatID);
}
}
return {
_: 'inputPeerUser',
user_id: peerID,
@ -158,11 +188,11 @@ const AppPeersManager = {
},
getPeerSearchText: (peerID: number) => {
var text
let text;
if(peerID > 0) {
text = '%pu ' + appUsersManager.getUserSearchText(peerID);
} else if(peerID < 0) {
var chat = appChatsManager.getChat(-peerID);
let chat = appChatsManager.getChat(-peerID);
text = '%pg ' + (chat.title || '');
}
return text;

View File

@ -0,0 +1,89 @@
import { RichTextProcessor } from "../richtextprocessor";
export type PollAnswer = {
_: 'pollAnswer',
text: string,
option: Uint8Array
};
export type PollAnswerVoters = {
_: 'pollAnswerVoters',
flags: number,
option: Uint8Array,
voters: number,
pFlags: Partial<{
chosen: true,
correct: true
}>
};
export type PollResult = {
_: 'pollAnswerVoters',
flags: number,
option: Uint8Array,
voters: number,
pFlags?: Partial<{chosen: true}>
};
export type PollResults = {
_: 'pollResults',
flags: number,
results?: Array<PollResult>,
total_voters?: number,
recent_voters?: number[],
solution?: string,
solution_entities?: any[],
pFlags: Partial<{
min: true
}>,
};
export type Poll = {
_: 'poll',
flags: number,
question: string,
id: string,
answers: Array<PollAnswer>,
close_period?: number,
close_date?: number
pFlags?: Partial<{
closed: true,
public_voters: true,
multiple_choice: true,
quiz: true
}>,
rQuestion?: string,
rReply?: string,
};
class AppPollsManager {
private polls: {[id: string]: Poll} = {};
private results: {[id: string]: PollResults} = {};
public savePoll(poll: Poll, results: PollResults) {
let id = poll.id;
if(this.polls[id]) {
this.results[id] = results;
return;
}
this.polls[id] = poll;
this.results[id] = results;
poll.rQuestion = RichTextProcessor.wrapEmojiText(poll.question);
poll.rReply = RichTextProcessor.wrapEmojiText('📊') + ' ' + (poll.rQuestion || 'poll');
}
public getPoll(pollID: string): {poll: Poll, results: PollResults} {
return {
poll: this.polls[pollID],
results: this.results[pollID]
};
}
}
export default new AppPollsManager();

View File

@ -5,17 +5,318 @@ import appImManager from "./appImManager";
//import apiManager from '../mtproto/apiManager';
import apiManager from '../mtproto/mtprotoworker';
import AppSearch, { SearchGroup } from "../../components/appSearch";
import { horizontalMenu } from "../../components/misc";
import { horizontalMenu, putPreloader } from "../../components/misc";
import appUsersManager from "./appUsersManager";
import Scrollable from "../../components/scrollable_new";
import appPhotosManager from "./appPhotosManager";
import { appPeersManager } from "../services";
import popupAvatar from "../../components/popupAvatar";
import appChatsManager from "./appChatsManager";
import { AppSelectPeers } from "../../components/appSelectPeers";
const SLIDERITEMSIDS = {
archived: 1,
contacts: 2
contacts: 2,
newChannel: 3,
addMembers: 4,
newGroup: 5,
};
interface SliderTab {
onClose?: () => void,
onCloseAfterTimeout?: () => void
}
class AppAddMembersTab implements SliderTab {
private container = document.querySelector('.addmembers-container') as HTMLDivElement;
private contentDiv = this.container.querySelector('.sidebar-content') as HTMLDivElement;
private backBtn = this.container.querySelector('.sidebar-close-button') as HTMLButtonElement;
private nextBtn = this.contentDiv.querySelector('.btn-corner') as HTMLButtonElement;
private selector: AppSelectPeers;
private peerType: 'channel' | 'chat';
private peerID: number; // always positive
private takeOut: (peerIDs: number[]) => void
constructor() {
this.nextBtn.addEventListener('click', () => {
let peerIDs = this.selector.getSelected();
if(peerIDs.length) {
if(this.takeOut) {
this.takeOut(peerIDs);
return;
}
this.nextBtn.classList.remove('tgico-next');
this.nextBtn.disabled = true;
putPreloader(this.nextBtn);
this.selector.freezed = true;
appChatsManager.inviteToChannel(this.peerID, peerIDs).then(() => {
this.backBtn.click();
});
}
});
}
public onCloseAfterTimeout() {
if(this.selector) {
this.selector.container.remove();
this.selector = null;
}
}
public init(id: number, type: 'channel' | 'chat', skipable: boolean, takeOut?: AppAddMembersTab['takeOut']) {
this.peerID = Math.abs(id);
this.peerType = type;
this.takeOut = takeOut;
this.onCloseAfterTimeout();
this.selector = new AppSelectPeers(this.contentDiv, skipable ? null : (length) => {
if(length) {
this.nextBtn.classList.add('is-visible');
} else {
this.nextBtn.classList.remove('is-visible');
}
}, 'contacts');
this.nextBtn.innerHTML = '';
this.nextBtn.disabled = false;
this.nextBtn.classList.add('tgico-next');
if(skipable) {
this.nextBtn.classList.add('is-visible');
} else {
this.nextBtn.classList.remove('is-visible');
}
appSidebarLeft.selectTab(SLIDERITEMSIDS.addMembers);
}
}
class AppNewChannelTab implements SliderTab {
private container = document.querySelector('.new-channel-container') as HTMLDivElement;
private canvas = this.container.querySelector('.avatar-edit-canvas') as HTMLCanvasElement;
private channelNameInput = this.container.querySelector('.new-channel-name') as HTMLInputElement;
private channelDescriptionInput = this.container.querySelector('.new-channel-description') as HTMLInputElement;
private nextBtn = this.container.querySelector('.btn-corner') as HTMLButtonElement;
private backBtn = this.container.querySelector('.sidebar-close-button') as HTMLButtonElement;
private uploadAvatar: () => Promise<any> = null;
constructor() {
this.container.querySelector('.avatar-edit').addEventListener('click', () => {
popupAvatar.open(this.canvas, (_upload) => {
this.uploadAvatar = _upload;
});
});
this.channelNameInput.addEventListener('input', () => {
let value = this.channelNameInput.value;
if(value.length) {
this.nextBtn.classList.add('is-visible');
} else {
this.nextBtn.classList.remove('is-visible');
}
});
this.nextBtn.addEventListener('click', () => {
let title = this.channelNameInput.value;
let about = this.channelDescriptionInput.value;
this.nextBtn.disabled = true;
appChatsManager.createChannel(title, about).then((channelID) => {
if(this.uploadAvatar) {
this.uploadAvatar().then((inputFile: any) => {
appChatsManager.editPhoto(channelID, inputFile);
});
}
appSidebarLeft.removeTabFromHistory(SLIDERITEMSIDS.newChannel);
appSidebarLeft.addMembersTab.init(channelID, 'channel', true);
});
});
}
public onCloseAfterTimeout() {
let ctx = this.canvas.getContext('2d');
ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
this.uploadAvatar = null;
this.channelNameInput.value = '';
this.channelDescriptionInput.value = '';
this.nextBtn.disabled = false;
}
}
class AppNewGroupTab implements SliderTab {
private container = document.querySelector('.new-group-container') as HTMLDivElement;
private contentDiv = this.container.querySelector('.sidebar-content') as HTMLDivElement;
private canvas = this.container.querySelector('.avatar-edit-canvas') as HTMLCanvasElement;
private groupNameInput = this.container.querySelector('.new-group-name') as HTMLInputElement;
private nextBtn = this.container.querySelector('.btn-corner') as HTMLButtonElement;
private searchGroup = new SearchGroup('', 'contacts', true, 'new-group-members disable-hover', false);
private uploadAvatar: () => Promise<any> = null;
private userIDs: number[];
constructor() {
this.container.querySelector('.avatar-edit').addEventListener('click', () => {
popupAvatar.open(this.canvas, (_upload) => {
this.uploadAvatar = _upload;
});
});
this.groupNameInput.addEventListener('input', () => {
let value = this.groupNameInput.value;
if(value.length) {
this.nextBtn.classList.add('is-visible');
} else {
this.nextBtn.classList.remove('is-visible');
}
});
this.nextBtn.addEventListener('click', () => {
let title = this.groupNameInput.value;
this.nextBtn.disabled = true;
appChatsManager.createChat(title, this.userIDs).then((chatID) => {
if(this.uploadAvatar) {
this.uploadAvatar().then((inputFile: any) => {
appChatsManager.editPhoto(chatID, inputFile);
});
}
appSidebarLeft.selectTab(0);
});
});
let chatsContainer = document.createElement('div');
chatsContainer.classList.add('chats-container');
chatsContainer.append(this.searchGroup.container);
let scrollable = new Scrollable(chatsContainer);
this.contentDiv.append(chatsContainer);
}
public onClose() {
}
public onCloseAfterTimeout() {
this.searchGroup.clear();
let ctx = this.canvas.getContext('2d');
ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
this.uploadAvatar = null;
this.groupNameInput.value = '';
this.nextBtn.disabled = false;
}
public init(userIDs: number[]) {
this.userIDs = userIDs;
appSidebarLeft.selectTab(SLIDERITEMSIDS.newGroup);
this.userIDs.forEach(userID => {
let {dom} = appDialogsManager.addDialog(userID, this.searchGroup.list, false, false);
let subtitle = '';
subtitle = appUsersManager.getUserStatusString(userID);
if(subtitle == 'online') {
subtitle = `<i>${subtitle}</i>`;
}
if(subtitle) {
dom.lastMessageSpan.innerHTML = subtitle;
}
});
this.searchGroup.nameEl.innerText = this.userIDs.length + ' members';
this.searchGroup.setActive();
}
}
class AppContactsTab implements SliderTab {
private container = document.getElementById('contacts-container');
private list = this.container.querySelector('#contacts') as HTMLUListElement;
private scrollable: Scrollable;
private promise: Promise<void>;
private input = this.container.querySelector('#contacts-search') as HTMLInputElement;
constructor() {
appDialogsManager.setListClickListener(this.list);
this.scrollable = new Scrollable(this.list.parentElement);
let prevValue = '';
this.input.addEventListener('input', () => {
let value = this.input.value;
if(prevValue != value) {
this.list.innerHTML = '';
this.openContacts(prevValue = value);
}
});
// preload contacts
appUsersManager.getContacts();
}
// need to clear, and left 1 page for smooth slide
public onClose() {
let pageCount = appPhotosManager.windowH / 72 * 1.25 | 0;
(Array.from(this.list.children) as HTMLElement[]).slice(pageCount).forEach(el => el.remove());
}
public onCloseAfterTimeout() {
this.list.innerHTML = '';
}
public openContacts(query?: string) {
appSidebarLeft.selectTab(SLIDERITEMSIDS.contacts);
if(this.promise) return this.promise;
this.scrollable.onScrolledBottom = null;
this.promise = appUsersManager.getContacts(query).then(contacts => {
this.promise = null;
if(appSidebarLeft.historyTabIDs[appSidebarLeft.historyTabIDs.length - 1] != SLIDERITEMSIDS.contacts) {
console.warn('user closed contacts before it\'s loaded');
return;
}
let sorted = contacts
.map(userID => {
let user = appUsersManager.getUser(userID);
let status = appUsersManager.getUserStatusForSort(user.status);
return {user, status};
})
.sort((a, b) => b.status - a.status);
let renderPage = () => {
let pageCount = appPhotosManager.windowH / 72 * 1.25 | 0;
let arr = sorted.splice(0, pageCount); // надо splice!
arr.forEach(({user}) => {
let {dialog, dom} = appDialogsManager.addDialog(user.id, this.list, false);
let status = appUsersManager.getUserStatusString(user.id);
dom.lastMessageSpan.innerHTML = status == 'online' ? `<i>${status}</i>` : status;
});
if(!sorted.length) renderPage = undefined;
};
renderPage();
this.scrollable.onScrolledBottom = () => {
if(renderPage) {
renderPage();
} else {
this.scrollable.onScrolledBottom = null;
}
};
});
}
}
class AppSidebarLeft {
private sidebarEl = document.getElementById('column-left') as HTMLDivElement;
private toolsBtn = this.sidebarEl.querySelector('.sidebar-tools-button') as HTMLButtonElement;
@ -24,12 +325,32 @@ class AppSidebarLeft {
private searchInput = document.getElementById('global-search') as HTMLInputElement;
private menuEl = this.toolsBtn.querySelector('.btn-menu');
private savedBtn = this.menuEl.querySelector('.menu-saved');
private archivedBtn = this.menuEl.querySelector('.menu-archive');
private newGroupBtn = this.menuEl.querySelector('.menu-new-group');
private contactsBtn = this.menuEl.querySelector('.menu-contacts');
private archivedBtn = this.menuEl.querySelector('.menu-archive');
private savedBtn = this.menuEl.querySelector('.menu-saved');
private logOutBtn = this.menuEl.querySelector('.menu-logout');
public archivedCount = this.archivedBtn.querySelector('.archived-count') as HTMLSpanElement;
private newBtnMenu = this.sidebarEl.querySelector('#new-menu');
private newButtons = {
channel: this.newBtnMenu.querySelector('.menu-channel'),
group: this.newBtnMenu.querySelector('.menu-group'),
privateChat: this.newBtnMenu.querySelector('.menu-private-chat'),
};
public newChannelTab = new AppNewChannelTab();
public addMembersTab = new AppAddMembersTab();
public contactsTab = new AppContactsTab();
public newGroupTab = new AppNewGroupTab();
private tabs: {[id: number]: SliderTab} = {
[SLIDERITEMSIDS.newChannel]: this.newChannelTab,
[SLIDERITEMSIDS.contacts]: this.contactsTab,
[SLIDERITEMSIDS.addMembers]: this.addMembersTab,
[SLIDERITEMSIDS.newGroup]: this.newGroupTab,
};
//private log = logger('SL');
private searchGroups = {
@ -42,12 +363,7 @@ class AppSidebarLeft {
private globalSearch = new AppSearch(this.searchContainer, this.searchInput, this.searchGroups);
private _selectTab: (id: number) => void;
private historyTabIDs: number[] = [];
private contactsList: HTMLUListElement;
private contactsScrollable: Scrollable;
private contactsPromise: Promise<void>;
private contactsInput: HTMLInputElement;
public historyTabIDs: number[] = [];
constructor() {
let peopleContainer = document.createElement('div');
@ -69,8 +385,7 @@ class AppSidebarLeft {
});
this.contactsBtn.addEventListener('click', (e) => {
this.openContacts();
this.selectTab(SLIDERITEMSIDS.contacts);
this.contactsTab.openContacts();
});
this.logOutBtn.addEventListener('click', (e) => {
@ -83,12 +398,6 @@ class AppSidebarLeft {
this.searchContainer.classList.remove('hide');
void this.searchContainer.offsetWidth; // reflow
this.searchContainer.classList.add('active');
/* if(!this.globalSearch.searchInput.value) {
for(let i in this.globalSearch.searchGroups) {
this.globalSearch.searchGroups[i].clear();
}
} */
false && this.searchInput.addEventListener('blur', (e) => {
if(!this.searchInput.value) {
@ -96,10 +405,6 @@ class AppSidebarLeft {
this.backBtn.classList.remove('active');
this.backBtn.click();
}
/* this.peerID = 0;
this.loadedCount = 0;
this.minMsgID = 0; */
}, {once: true});
});
@ -118,6 +423,18 @@ class AppSidebarLeft {
}, 150);
});
this.newButtons.channel.addEventListener('click', (e) => {
this.selectTab(SLIDERITEMSIDS.newChannel);
});
[this.newButtons.group, this.newGroupBtn].forEach(btn => {
btn.addEventListener('click', (e) => {
this.addMembersTab.init(0, 'chat', false, (peerIDs) => {
this.newGroupTab.init(peerIDs);
});
});
});
$rootScope.$on('dialogs_archived_unread', (e: CustomEvent) => {
this.archivedCount.innerText = '' + e.detail.count;
});
@ -125,22 +442,14 @@ class AppSidebarLeft {
this._selectTab = horizontalMenu(null, this.sidebarEl.querySelector('.sidebar-slider') as HTMLDivElement, null, null, 420);
this._selectTab(0);
let onCloseBtnClick = () => {
console.log('sidebar-close-button click:', this.historyTabIDs);
let closingID = this.historyTabIDs.pop(); // pop current
this.onCloseTab(closingID);
this._selectTab(this.historyTabIDs.pop() || 0);
};
Array.from(this.sidebarEl.querySelectorAll('.sidebar-close-button') as any as HTMLElement[]).forEach(el => {
el.addEventListener('click', () => {
console.log('sidebar-close-button click:', this.historyTabIDs);
let closingID = this.historyTabIDs.pop(); // pop current
// need to clear, and left 1 page for smooth slide
if(closingID == SLIDERITEMSIDS.contacts) {
let pageCount = appPhotosManager.windowH / 72 * 1.25 | 0;
(Array.from(this.contactsList.children) as HTMLElement[]).slice(pageCount).forEach(el => el.remove());
setTimeout(() => {
this.contactsList.innerHTML = '';
}, 420);
}
this._selectTab(this.historyTabIDs.pop() || 0);
});
el.addEventListener('click', onCloseBtnClick);
});
appUsersManager.getTopPeers().then(categories => {
@ -162,80 +471,34 @@ class AppSidebarLeft {
this.searchGroups.people.setActive();
});
});
let contactsContainer = this.sidebarEl.querySelector('#contacts-container');
this.contactsInput = contactsContainer.querySelector('#contacts-search');
this.contactsList = contactsContainer.querySelector('#contacts') as HTMLUListElement;
appDialogsManager.setListClickListener(this.contactsList);
this.contactsScrollable = new Scrollable(this.contactsList.parentElement);
let prevValue = '';
this.contactsInput.addEventListener('input', () => {
let value = this.contactsInput.value;
if(prevValue != value) {
this.contactsList.innerHTML = '';
this.openContacts(prevValue = value);
}
});
// preload contacts
appUsersManager.getContacts();
}
public openContacts(query?: string) {
if(this.contactsPromise) return this.contactsPromise;
this.contactsScrollable.onScrolledBottom = null;
this.contactsPromise = appUsersManager.getContacts(query).then(contacts => {
this.contactsPromise = null;
if(this.historyTabIDs[this.historyTabIDs.length - 1] != SLIDERITEMSIDS.contacts) {
console.warn('user closed contacts before it\'s loaded');
return;
}
let sorted = contacts
.map(userID => {
let user = appUsersManager.getUser(userID);
let status = appUsersManager.getUserStatusForSort(user.status);
return {user, status};
})
.sort((a, b) => b.status - a.status);
let renderPage = () => {
let pageCount = appPhotosManager.windowH / 72 * 1.25 | 0;
let arr = sorted.splice(0, pageCount);
arr.forEach(({user}) => {
let {dialog, dom} = appDialogsManager.addDialog(user.id, this.contactsList, false);
let status = appUsersManager.getUserStatusString(user.id);
dom.lastMessageSpan.innerHTML = status == 'online' ? `<i>${status}</i>` : status;
});
if(!sorted.length) renderPage = undefined;
};
renderPage();
this.contactsScrollable.onScrolledBottom = () => {
if(renderPage) {
renderPage();
} else {
this.contactsScrollable.onScrolledBottom = null;
}
};
});
}
public selectTab(id: number) {
this.historyTabIDs.push(id);
this._selectTab(id);
}
public removeTabFromHistory(id: number) {
this.historyTabIDs.findAndSplice(i => i == id);
this.onCloseTab(id);
}
public onCloseTab(id: number) {
let tab = this.tabs[id];
if(tab) {
if('onClose' in tab) {
tab.onClose();
}
if('onCloseAfterTimeout' in tab) {
setTimeout(() => {
tab.onCloseAfterTimeout();
}, 420);
}
}
}
}
const appSidebarLeft = new AppSidebarLeft();
(window as any).appSidebarLeft = appSidebarLeft;
export default appSidebarLeft;

View File

@ -158,12 +158,19 @@ export class AppUsersManager {
contactsList.sort((userID1: number, userID2: number) => {
const sortName1 = (this.users[userID1] || {}).sortName || '';
const sortName2 = (this.users[userID2] || {}).sortName || '';
return sortName1.localeCompare(sortName2);
});
/* contactsList.sort((userID1: number, userID2: number) => {
const sortName1 = (this.users[userID1] || {}).sortName || '';
const sortName2 = (this.users[userID2] || {}).sortName || '';
if(sortName1 == sortName2) {
return 0;
}
}
return sortName1 > sortName2 ? 1 : -1;
});
}); */
return contactsList;
});
@ -215,7 +222,8 @@ 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 ? '' : SearchIndexManager.cleanSearchText(apiUser.first_name + ' ' + (apiUser.last_name || ''));
apiUser.sortName = apiUser.pFlags.deleted ? '' : apiUser.first_name + ' ' + (apiUser.last_name || '');
var nameWords = apiUser.sortName.split(' ');
var firstWord = nameWords.shift();

View File

@ -345,17 +345,17 @@ export function longFromInts(high: number, low: number) {
export function intToUint(val: number | string) {
if(typeof(val) === 'string') val = parseInt(val);
if(val < 0) {
/* if(val < 0) {
val = val + 4294967296;
}
} */
return val;
}
export function uintToInt(val: number) {
if(val > 2147483647) {
/* if(val > 2147483647) {
val = val - 4294967296;
}
} */
return val;
}

View File

@ -157,7 +157,5 @@ class LottieLoader {
}
const lottieLoader = new LottieLoader();
//(window as any).LottieLoader = lottieLoader;
(window as any).LottieLoader = lottieLoader;
export default lottieLoader;

View File

@ -227,14 +227,14 @@ class MTPNetworker {
if(!this.connectionInited) { // this will call once for each new session
///////this.log('Wrap api call !this.connectionInited');
let invokeWithLayer = Schema.API.methods.find((m: any) => m.method == 'invokeWithLayer');
let invokeWithLayer = Schema.API.methods.find(m => m.method == 'invokeWithLayer');
if(!invokeWithLayer) throw new Error('no invokeWithLayer!');
serializer.storeInt(+invokeWithLayer.id >>> 0, 'invokeWithLayer');
// @ts-ignore
serializer.storeInt(Schema.layer, 'layer');
let initConnection = Schema.API.methods.find((m: any) => m.method == 'initConnection');
let initConnection = Schema.API.methods.find(m => m.method == 'initConnection');
if(!initConnection) throw new Error('no initConnection!');
serializer.storeInt(+initConnection.id >>> 0, 'initConnection');
@ -260,7 +260,7 @@ class MTPNetworker {
}
if(options.afterMessageID) {
let invokeAfterMsg = Schema.API.methods.find((m: any) => m.method == 'invokeAfterMsg');
let invokeAfterMsg = Schema.API.methods.find(m => m.method == 'invokeAfterMsg');
if(!invokeAfterMsg) throw new Error('no invokeAfterMsg!');
this.log('Api call options.afterMessageID!');

File diff suppressed because one or more lines are too long

View File

@ -16,10 +16,10 @@ import {gzipUncompress} from '../crypto/crypto_utils';
import {gzipUncompress} from '../bin_utils';
/// #endif
const boolFalse = +Schema.API.constructors.find((c: any) => c.predicate == 'boolFalse').id >>> 0;
const boolTrue = +Schema.API.constructors.find((c: any) => c.predicate == 'boolTrue').id >>> 0;
const vector = +Schema.API.constructors.find((c: any) => c.predicate == 'vector').id >>> 0;
const gzipPacked = +Schema.MTProto.constructors.find((c: any) => c.predicate == 'gzip_packed').id >>> 0;
const boolFalse = +Schema.API.constructors.find(c => c.predicate == 'boolFalse').id >>> 0;
const boolTrue = +Schema.API.constructors.find(c => c.predicate == 'boolTrue').id >>> 0;
const vector = +Schema.API.constructors.find(c => c.predicate == 'vector').id >>> 0;
const gzipPacked = +Schema.MTProto.constructors.find(c => c.predicate == 'gzip_packed').id >>> 0;
//console.log('boolFalse', boolFalse == 0xbc799737);
@ -628,7 +628,7 @@ class TLDeserialization {
return result;
}
var schema = (this.mtproto ? Schema.MTProto : Schema.API) as any;
var schema = this.mtproto ? Schema.MTProto : Schema.API;
var predicate = false;
var constructorData: any = false;
@ -675,7 +675,7 @@ class TLDeserialization {
}
}
var i: number = index[constructorCmp];
var i = index[constructorCmp];
if(i) {
constructorData = schema.constructors[i];
}

View File

@ -367,7 +367,7 @@ export const langPack = {
"messageActionChatJoined": "joined the group",
"messageActionChatAddUser": "invited {user}",
"messageActionChatAddUsers": "invited {} users",
"messageActionChatLeave": "left group",
"messageActionChatLeave": "left the group",
"messageActionChatDeleteUser": "removed user",
"messageActionChatJoinedByLink": "joined the group",
"messageActionPinMessage": "pinned message",

View File

@ -1,4 +1,4 @@
import { openBtnMenu, ripple } from "../components/misc";
import { openBtnMenu/* , ripple */ } from "../components/misc";
//import {stackBlurImage} from '../lib/StackBlur';
import Page from "./page";
@ -44,7 +44,7 @@ let onFirstMount = () => import('../lib/appManagers/appImManager').then(() => {/
import('../lib/services');
(Array.from(document.getElementsByClassName('rp')) as HTMLElement[]).forEach(el => ripple(el));
//(Array.from(document.getElementsByClassName('rp')) as HTMLElement[]).forEach(el => ripple(el));
Array.from(document.getElementsByClassName('btn-menu-toggle')).forEach((el) => {
el.addEventListener('click', (e) => {

View File

@ -1,10 +1,9 @@
import {putPreloader} from '../components/misc';
import resizeableImage from '../lib/cropper';
import pageIm from './pageIm';
//import apiManager from '../lib/mtproto/apiManager';
import apiManager from '../lib/mtproto/mtprotoworker';
import apiFileManager from '../lib/mtproto/apiFileManager';
import Page from './page';
import popupAvatar from '../components/popupAvatar';
let authCode: {
'phone_number': string,
@ -13,84 +12,13 @@ let authCode: {
let onFirstMount = () => {
const pageElement = page.pageEl;
const avatarInput = document.getElementById('avatar-input') as HTMLInputElement;
const avatarPopup = document.getElementsByClassName('popup-avatar')[0];
const avatarPreview = pageElement.querySelector('#canvas-avatar') as HTMLCanvasElement;
const cropContainer = avatarPopup.getElementsByClassName('crop')[0] as HTMLDivElement;
let avatarImage = new Image();
cropContainer.append(avatarImage);
let avatarBlob: Blob;
(avatarPopup.getElementsByClassName('popup-close')[0] as HTMLButtonElement)
.addEventListener('click', function(this, e) {
/* let popup = findUpClassName(this, 'popup');
popup.classList.remove('active'); */
setTimeout(() => {
cropper.removeHandlers();
if(avatarImage) {
avatarImage.remove();
}
}, 200);
/* e.cancelBubble = true;
return false; */
});
let cropper = {
crop: () => {},
removeHandlers: () => {}
};
// apply
avatarPopup.getElementsByClassName('btn-crop')[0].addEventListener('click', () => {
cropper.crop();
avatarPopup.classList.remove('active');
cropper.removeHandlers();
avatarPreview.toBlob(blob => {
avatarBlob = blob; // save blob to send after reg
// darken
let ctx = avatarPreview.getContext('2d');
ctx.fillStyle = "rgba(0, 0, 0, 0.3)";
ctx.fillRect(0, 0, avatarPreview.width, avatarPreview.height);
}, 'image/jpeg', 1);
avatarImage.remove();
});
avatarInput.addEventListener('change', (e: any) => {
var file = e.target.files[0];
if(!file) {
return;
}
var reader = new FileReader();
reader.onload = (e) => {
var contents = e.target.result as string;
avatarImage = new Image();
cropContainer.append(avatarImage);
avatarImage.src = contents;
avatarImage.onload = () => {
/* let {w, h} = calcImageInBox(avatarImage.naturalWidth, avatarImage.naturalHeight, 460, 554);
cropContainer.style.width = w + 'px';
cropContainer.style.height = h + 'px'; */
avatarPopup.classList.add('active');
cropper = resizeableImage(avatarImage, avatarPreview);
avatarInput.value = '';
};
};
reader.readAsDataURL(file);
}, false);
let uploadAvatar: () => Promise<any>;
pageElement.querySelector('.auth-image').addEventListener('click', () => {
avatarInput.click();
popupAvatar.open(avatarPreview, (_uploadAvatar) => {
uploadAvatar = _uploadAvatar;
});
});
const headerName = pageElement.getElementsByClassName('fullName')[0] as HTMLHeadingElement;
@ -108,13 +36,13 @@ let onFirstMount = () => {
};
let sendAvatar = () => new Promise((resolve, reject) => {
if(!avatarBlob) {
if(!uploadAvatar) {
console.log('User has not selected avatar');
return resolve();
}
console.log('invoking uploadFile...');
apiFileManager.uploadFile(avatarBlob).then((inputFile: any) => {
uploadAvatar().then((inputFile: any) => {
console.log('uploaded smthn', inputFile);
apiManager.invokeApi('photos.uploadProfilePhoto', {

4
src/schema.json Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

1
src/schema_pretty.json Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -176,12 +176,23 @@
//vertical-align: unset;
margin-top: -1.5px;
}
&.is-verified:after {
content: " ";
background: url(/assets/img/icon-verified.svg);
display: inline-block;
width: 20px;
height: 20px;
vertical-align: text-bottom;
margin-left: 2px;
}
}
.user-last-message {
img.emoji {
width: 20px;
height: 20px;
margin-top: -3px;
}
span.emoji {
@ -193,11 +204,11 @@
}
.user-title, .user-last-message {
max-width: 80%;
max-width: 86%;
i {
font-style: normal;
color: $darkblue;
color: $color-blue;
}
}
@ -207,7 +218,7 @@
margin-top: -.3rem;
&[class*=" tgico-"] {
color: $success-color;
color: $color-green;
font-size: 1.25rem;
}
@ -242,7 +253,7 @@
}
.unread {
background: $success-color;
background: $color-green;
}
.unread-muted, .tgico-pinnedchat {
@ -269,6 +280,10 @@
padding-bottom: 17px;
}
}
&:last-child {
border-bottom: none;
}
}
}

View File

@ -124,6 +124,22 @@
}
}
}
.item-main .sidebar-content {
.btn-menu {
bottom: calc(100% + 10px);
}
&:hover {
.btn-corner {
transform: translateY(0px);
&.menu-open:before {
content: $tgico-close;
}
}
}
}
}
#search-container {
@ -138,3 +154,33 @@
opacity: 1;
}
}
.new-channel-container, .new-group-container {
.sidebar-content {
flex-direction: column;
}
.avatar-edit {
width: 120px;
height: 120px;
margin: 1px auto 32px;
flex: 0 0 auto;
}
.input-wrapper {
width: 380px;
margin: 0 auto;
flex: 0 0 auto;
}
.chats-container {
flex: 1 1 auto;
}
.caption {
font-size: 14px;
margin-top: 14px;
margin-left: 23px;
color: #707579;
}
}

View File

@ -5,7 +5,7 @@
right: 0;
bottom: 0;
background: rgba(0, 0, 0, .88);
/* color: $darkgrey; */
/* color: $color-gray; */
display: flex;
align-items: center;
justify-content: center;
@ -122,7 +122,7 @@
.media-viewer-caption {
flex: 1;
text-align: center;
color: $darkgrey;
color: $color-gray;
transition: .2s;
max-width: 50vw;
word-break: break-word;

View File

@ -98,12 +98,12 @@
&-subtitle {
text-align: center;
color: $darkgrey;
color: $color-gray;
font-size: 14px;
margin-bottom: 2px;
&.online {
color: $darkblue;
color: $color-blue;
}
}
@ -123,7 +123,7 @@
left: 24px;
/* top: 0; */
font-size: 24px;
color: $darkgrey;
color: $color-gray;
}
p {
@ -328,7 +328,7 @@
font-size: 2rem;
color: #fff;
text-transform: uppercase;
background-color: $blue;
background-color: $color-blue;
}
}

View File

@ -100,7 +100,7 @@ div.scrollable::-webkit-scrollbar-thumb {
// BROWSER SCROLL
div.scrollable-y::-webkit-scrollbar {
width: .375rem;
height: 200px;
//height: 200px;
}
/* div.scrollable-y::-webkit-scrollbar-thumb {

View File

@ -1,5 +1,5 @@
.menu-horizontal {
color: $darkgrey;
color: $color-gray;
border-bottom: 1px solid $lightgrey;
position: relative;
@ -25,13 +25,13 @@
font-weight: 500;
&.active {
color: $blue;
color: $color-blue;
}
}
&__stripe {
position: absolute;
background: $blue;
background: $color-blue;
//left: 0;
left: -2px;
transition: .3s transform, .3s width;

View File

@ -3,31 +3,20 @@ $border-radius: 8px;
$border-radius-medium: 10px;
$border-radius-big: 12px;
$button-primary-background: #4EA4F6;
$success-color: #4DCD5E;
$color-green: #4DCD5E;
$color-error: #E53935;
$color-gray: #707579;
$color-blue: #50a2e9;
$lightblue: #e6ebee;
$blue: #50a2e9;
$darkblue: #50a2e9;
$lightgreen: #eeffde;
$green: #4dcd5e;
$darkgreen: #50af4f;
$dotgreen: #0ac630;
$color-text-green: $darkgreen;
$lightgrey: #dadce0;
$grey: #c4c9cc;
$darkgrey: #707579;
$light: rgba($darkgrey, 0.08);
$text: #000000;
$bg: #ffffff;
$light: rgba($color-gray, 0.08);
$text-size: 16px;
$time-size: 12px;
$large-screen: 1680px;
//$large-screen: 16800px;
@ -35,6 +24,7 @@ $large-screen: 1680px;
@import "partials/ico";
@import "partials/chatlist";
@import "partials/chat";
@import "partials/chatBubble";
@import "partials/sidebar";
@import "partials/leftSidebar";
@import "partials/rightSidebar";
@ -59,7 +49,7 @@ html {
}
a {
color: $blue;
color: $color-blue;
}
button, input, optgroup, select, textarea, html {
@ -163,7 +153,7 @@ input {
justify-content: center;
&.active {
color: $blue;
color: $color-blue;
}
&:hover {
@ -188,15 +178,6 @@ input {
color: $color-error!important;
}
.btn-menu-toggle {
position: relative;
overflow: visible;
&.menu-open {
background-color: rgba(112, 117, 121, 0.08);
}
}
.btn-menu {
visibility: hidden;
position: absolute;
@ -294,7 +275,7 @@ input {
height: 54px;
line-height: 54px;
border-radius: 50%;
background-color: $blue;
background-color: $color-blue;
text-align: center;
font-size: 1.25em;
/* overflow: hidden; */
@ -412,7 +393,7 @@ input {
height: 70px;
&-ico {
background-color: $blue;
background-color: $color-blue;
border-radius: 5px;
line-height: 10px;
@ -442,7 +423,7 @@ input {
}
&-download {
background-color: $blue;
background-color: $color-blue;
border-radius: 8px;
}
@ -490,7 +471,7 @@ input {
&-size {
white-space: nowrap;
color: $darkgrey;
color: $color-gray;
font-size: 14px;
padding-right: 32px;
line-height: 1.3;
@ -547,7 +528,7 @@ input {
&-toggle, &-download {
border-radius: 50%;
background-color: $blue;
background-color: $color-blue;
font-size: 2.3rem;
align-items: center;
}
@ -566,7 +547,7 @@ input {
fill: #CBCBCB;
&.active {
fill: $blue;
fill: $color-blue;
}
}
}
@ -670,32 +651,36 @@ input {
}
} */
.avatar-edit {
position: relative;
border-radius: 50%;
cursor: pointer;
overflow: hidden;
&-canvas {
max-width: 100%;
max-height: 100%;
width: 100%;
height: 100%;
background-color: $color-blue;
}
.tgico-cameraadd {
position: absolute;
font-size: 48px;
line-height: 48px;
top: 50%;
left: 50%;
transform: translateY(-50%) translateX(-50%);
z-index: 2;
color: #fff;
}
}
.page-signUp {
.auth-image {
border-radius: 50%;
cursor: pointer;
position: relative;
overflow: hidden;
margin-top: 10px;
margin-bottom: 14px;
canvas {
max-width: 100%;
max-height: 100%;
width: 100%;
height: 100%;
background-color: $blue;
}
svg {
position: absolute;
width: 48px;
height: 48px;
top: 50%;
left: 50%;
transform: translateY(-50%) translateX(-50%);
z-index: 2;
}
}
}
@ -758,6 +743,10 @@ input {
position: relative;
z-index: 1;
/* font-weight: 500; */
/* &:hover {
border-color: #000;
} */
&:focus {
border-color: $button-primary-background;
@ -951,16 +940,16 @@ input {
}
::placeholder { /* Chrome, Firefox, Opera, Safari 10.1+ */
color: $placeholder-color;
color: #a2acb4;
opacity: 1; /* Firefox */
}
:-ms-input-placeholder { /* Internet Explorer 10-11 */
color: $placeholder-color;
color: #a2acb4;
}
::-ms-input-placeholder { /* Microsoft Edge */
color: $placeholder-color;
color: #a2acb4;
}
input:focus, button:focus {
@ -1019,7 +1008,7 @@ input:focus, button:focus {
} */
.btn-primary {
background: $blue;
background: $color-blue;
color: #fff;
border-radius: $border-radius-medium;
width: 100%;
@ -1033,7 +1022,7 @@ input:focus, button:focus {
padding: 0; // new
&:hover {
background: darken($blue, 8%);
background: darken($color-blue, 8%);
}
svg, use {
@ -1058,6 +1047,16 @@ input:focus, button:focus {
}
}
.btn-menu-toggle {
position: relative;
overflow: visible !important;
font-weight: normal !important;
&:not(.btn-primary).menu-open {
background-color: rgba(112, 117, 121, 0.08);
}
}
.preloader {
&-circular {
animation: rotate 2s linear infinite;
@ -1336,6 +1335,12 @@ img.emoji {
display: flex;
max-height: 100vh;
min-height: 100vh;
.avatar-edit {
.tgico-cameraadd {
top: 52%;
}
}
> div {
height: 100%;