diff --git a/package-lock.json b/package-lock.json
index fd811677..dfe7db45 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -11779,6 +11779,21 @@
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
"integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A=="
},
+ "qr-code-styling": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/qr-code-styling/-/qr-code-styling-1.0.1.tgz",
+ "integrity": "sha512-+pxRN6qMU7CJ9tJHKGfAGZS/KmNjpJFnc5I6lI/cW5/bEC9/bsGO5ADip8ob7/xeSoppH+P73ylA7Kn/07tclA==",
+ "dev": true,
+ "requires": {
+ "qrcode-generator": "^1.4.3"
+ }
+ },
+ "qrcode-generator": {
+ "version": "1.4.4",
+ "resolved": "https://registry.npmjs.org/qrcode-generator/-/qrcode-generator-1.4.4.tgz",
+ "integrity": "sha512-HM7yY8O2ilqhmULxGMpcHSF1EhJJ9yBj8gvDEuZ6M+KGJ0YY2hKpnXvRD+hZPLrDVck3ExIGhmPtSdcjC+guuw==",
+ "dev": true
+ },
"qs": {
"version": "6.7.0",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz",
diff --git a/package.json b/package.json
index 9531a742..bf115537 100644
--- a/package.json
+++ b/package.json
@@ -46,6 +46,7 @@
"npm": "^6.14.4",
"on-build-webpack": "^0.1.0",
"pako": "^1.0.11",
+ "qr-code-styling": "^1.0.1",
"resolve-url-loader": "^3.1.1",
"sass-loader": "^8.0.2",
"streamsaver": "^2.0.4",
diff --git a/src/components/appForward.ts b/src/components/appForward.ts
new file mode 100644
index 00000000..278d4594
--- /dev/null
+++ b/src/components/appForward.ts
@@ -0,0 +1,230 @@
+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', '
');
+
+ 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 = `${subtitle}`;
+ }
+ }
+
+ 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);
+ }
+}
+
+class AppForward {
+ private container = document.getElementById('forward-container') as HTMLDivElement;
+ private closeBtn = this.container.querySelector('.sidebar-close-button') as HTMLButtonElement;
+ private sendBtn = this.container.querySelector('.btn-circle') as HTMLButtonElement;
+
+ private selector: AppSelectPeers;
+ private msgIDs: number[] = [];
+
+ constructor() {
+ this.closeBtn.addEventListener('click', () => {
+ this.cleanup();
+ this.container.classList.remove('active');
+ appSidebarRight.onSidebarScroll();
+ });
+
+ this.sendBtn.addEventListener('click', () => {
+ let peerIDs = this.selector.getSelected();
+
+ if(this.msgIDs.length && peerIDs.length) {
+ this.sendBtn.classList.remove('tgico-send');
+ this.sendBtn.disabled = true;
+ putPreloader(this.sendBtn);
+ this.selector.freezed = true;
+
+ let s = () => {
+ let promises = peerIDs.splice(0, 3).map(peerID => {
+ return appMessagesManager.forwardMessages(peerID, this.msgIDs);
+ });
+
+ Promise.all(promises).then(() => {
+ if(peerIDs.length) {
+ return s();
+ } else {
+ this.closeBtn.click();
+ }
+ });
+ };
+
+ s();
+ }
+ });
+ }
+
+ public cleanup() {
+ if(this.selector) {
+ this.selector.container.remove();
+ this.selector = null;
+ }
+ }
+
+ public init(ids: number[]) {
+ this.cleanup();
+ this.msgIDs = ids;
+
+ appSidebarRight.toggleSidebar(true);
+ this.container.classList.add('active');
+ this.sendBtn.innerHTML = '';
+ this.sendBtn.classList.add('tgico-send');
+ this.sendBtn.disabled = false;
+
+ this.selector = new AppSelectPeers(this.container, (length) => {
+ if(length) {
+ this.sendBtn.classList.add('is-visible');
+ } else {
+ this.sendBtn.classList.remove('is-visible');
+ }
+ });
+ }
+}
+
+export default new AppForward();
\ No newline at end of file
diff --git a/src/components/appSearch.ts b/src/components/appSearch.ts
index 097ca3d8..5f236775 100644
--- a/src/components/appSearch.ts
+++ b/src/components/appSearch.ts
@@ -4,17 +4,19 @@ import appMessagesIDsManager from "../lib/appManagers/appMessagesIDsManager";
import appUsersManager from "../lib/appManagers/appUsersManager";
import appPeersManager from '../lib/appManagers/appPeersManager';
import appMessagesManager from "../lib/appManagers/appMessagesManager";
-import { numberWithCommas, escapeRegExp } from "../lib/utils";
+import { escapeRegExp } from "../lib/utils";
import { formatPhoneNumber } from "./misc";
+import appChatsManager from "../lib/appManagers/appChatsManager";
export class SearchGroup {
container: HTMLDivElement;
nameEl: HTMLDivElement;
list: HTMLUListElement;
- constructor(public name: string, public type: string) {
+ constructor(public name: string, public type: string, private clearable = true, className?: string) {
this.list = document.createElement('ul');
this.container = document.createElement('div');
+ if(className) this.container.classList.add(className);
this.nameEl = document.createElement('div');
this.nameEl.classList.add('search-group__name');
this.nameEl.innerText = name;
@@ -28,7 +30,10 @@ export class SearchGroup {
clear() {
this.container.style.display = 'none';
- this.list.innerHTML = '';
+
+ if(this.clearable) {
+ this.list.innerHTML = '';
+ }
}
setActive() {
@@ -47,7 +52,7 @@ export default class AppSearch {
private query = '';
- private listsContainer: HTMLDivElement = null;
+ public listsContainer: HTMLDivElement = null;
private peerID = 0; // 0 - means global
@@ -155,11 +160,7 @@ export default class AppSearch {
if(showMembersCount && (peer.participants_count || peer.participants)) {
let regExp = new RegExp(`(${escapeRegExp(query)})`, 'gi');
dom.titleSpan.innerHTML = dom.titleSpan.innerHTML.replace(regExp, '$1');
-
- let isChannel = appPeersManager.isChannel(peerID) && !appPeersManager.isMegagroup(peerID);
- let participants_count = peer.participants_count || peer.participants.participants.length;
- let subtitle = numberWithCommas(participants_count) + ' ' + (isChannel ? 'subscribers' : 'members');
- dom.lastMessageSpan.innerText = subtitle;
+ dom.lastMessageSpan.innerText = appChatsManager.getChatMembersString(-peerID);
} else {
let username = appPeersManager.getPeerUsername(peerID);
if(!username) {
diff --git a/src/components/bubbleGroups.ts b/src/components/bubbleGroups.ts
index fc02cb92..4dbd0223 100644
--- a/src/components/bubbleGroups.ts
+++ b/src/components/bubbleGroups.ts
@@ -154,6 +154,7 @@ export default class BubbleGroups {
cleanup() {
this.bubblesByGroups = [];
+ this.groups = [];
/* for(let value of this.updateRAFs.values()) {
window.cancelAnimationFrame(value);
}
diff --git a/src/components/chatInput.ts b/src/components/chatInput.ts
index e9406fe4..f1a4ffee 100644
--- a/src/components/chatInput.ts
+++ b/src/components/chatInput.ts
@@ -393,7 +393,7 @@ export class ChatInput {
if(!file) continue;
willAttach.type = file.type.indexOf('image/') === 0 ? 'media' : "document";
- attachFile(file);
+ attachFiles([file]);
}
}
}, true);
@@ -559,12 +559,12 @@ export class ChatInput {
this.onMessageSent(!this.editMsgID);
};
- public setTopInfo(title: string, subtitle: string, input?: string, media?: any) {
+ public setTopInfo(title: string, subtitle: string, input?: string, message?: any) {
//appImManager.scrollPosition.prepareFor('down');
if(this.replyElements.container.lastElementChild.tagName == 'DIV') {
this.replyElements.container.lastElementChild.remove();
- this.replyElements.container.append(wrapReply(title, subtitle, media));
+ this.replyElements.container.append(wrapReply(title, subtitle, message));
}
//this.replyElements.titleEl.innerHTML = title ? RichTextProcessor.wrapEmojiText(title) : '';
//this.replyElements.subtitleEl.innerHTML = subtitle ? RichTextProcessor.wrapEmojiText(subtitle) : '';
diff --git a/src/components/emoticonsDropdown.ts b/src/components/emoticonsDropdown.ts
index c86596ab..c38d30a4 100644
--- a/src/components/emoticonsDropdown.ts
+++ b/src/components/emoticonsDropdown.ts
@@ -141,6 +141,15 @@ const initEmoticonsDropdown = (pageEl: HTMLDivElement,
let div = document.createElement('div');
div.classList.add('emoji-category');
+ let titleDiv = document.createElement('div');
+ titleDiv.classList.add('category-title');
+ titleDiv.innerText = category;
+
+ let itemsDiv = document.createElement('div');
+ itemsDiv.classList.add('category-items');
+
+ div.append(titleDiv, itemsDiv);
+
let emojis = sorted[category];
emojis.forEach(details => {
let emoji = details.unified;
@@ -161,7 +170,7 @@ const initEmoticonsDropdown = (pageEl: HTMLDivElement,
//spanEmoji = spanEmoji.firstElementChild as HTMLSpanElement;
//spanEmoji.setAttribute('emoji', emoji);
- div.appendChild(spanEmoji);
+ itemsDiv.appendChild(spanEmoji);
});
divs[category] = div;
@@ -272,17 +281,23 @@ const initEmoticonsDropdown = (pageEl: HTMLDivElement,
let heights: number[] = [];
let heightRAF = 0;
- let categoryPush = (categoryDiv: HTMLDivElement, docs: MTDocument[], prepend?: boolean) => {
+ let categoryPush = (categoryDiv: HTMLDivElement, categoryTitle: string, docs: MTDocument[], prepend?: boolean) => {
//if((docs.length % 5) != 0) categoryDiv.classList.add('not-full');
- let container = document.createElement('div');
- categoryDiv.append(container);
+ let itemsDiv = document.createElement('div');
+ itemsDiv.classList.add('category-items');
+
+ let titleDiv = document.createElement('div');
+ titleDiv.classList.add('category-title');
+ titleDiv.innerText = categoryTitle;
+
+ categoryDiv.append(titleDiv, itemsDiv);
docs.forEach(doc => {
let div = document.createElement('div');
wrapSticker(doc, div, undefined, lazyLoadQueue, EMOTICONSSTICKERGROUP, true, false, true);
- container.append(div);
+ itemsDiv.append(div);
});
if(prepend) stickersScroll.prepend(categoryDiv);
@@ -356,7 +371,7 @@ const initEmoticonsDropdown = (pageEl: HTMLDivElement,
//stickersScroll.prepend(categoryDiv);
- categoryPush(categoryDiv, stickers.stickers, true);
+ categoryPush(categoryDiv, 'Recent', stickers.stickers, true);
}),
apiManager.invokeApi('messages.getAllStickers', {hash: 0}).then(async(res) => {
@@ -413,7 +428,7 @@ const initEmoticonsDropdown = (pageEl: HTMLDivElement,
wrapSticker(stickerSet.documents[0], li as any, undefined, undefined, EMOTICONSSTICKERGROUP); // kostil
}
- categoryPush(categoryDiv, stickerSet.documents, false);
+ categoryPush(categoryDiv, stickerSet.set.title, stickerSet.documents, false);
}
})
]);
diff --git a/src/components/lazyLoadQueue.ts b/src/components/lazyLoadQueue.ts
index 1120a942..bd478e62 100644
--- a/src/components/lazyLoadQueue.ts
+++ b/src/components/lazyLoadQueue.ts
@@ -15,6 +15,7 @@ export default class LazyLoadQueue {
private unlockResolve: () => void = null;
private log = console.log.bind(console, '[LL]:');
+ private debug = false;
constructor(private parallelLimit = 5) {
@@ -69,13 +70,14 @@ export default class LazyLoadQueue {
let tempID = this.tempID;
- this.log('will load media', this.lockPromise, item);
+ this.debug && this.log('will load media', this.lockPromise, item);
try {
if(this.lockPromise) {
let perf = performance.now();
- await this.lockPromise;
- this.log('waited lock:', performance.now() - perf);
+ await this.lockPromise;
+
+ this.debug && this.log('waited lock:', performance.now() - perf);
}
await new Promise((resolve, reject) => window.requestAnimationFrame(() => window.requestAnimationFrame(resolve)));
@@ -88,7 +90,7 @@ export default class LazyLoadQueue {
this.loadingMedia--;
}
- this.log('loaded media');
+ this.debug && this.log('loaded media');
if(this.lazyLoadMedia.length) {
this.processQueue();
diff --git a/src/components/misc.ts b/src/components/misc.ts
index 69bc2d75..2e4ef7a5 100644
--- a/src/components/misc.ts
+++ b/src/components/misc.ts
@@ -107,10 +107,13 @@ let set = (elem: HTMLElement | HTMLImageElement | SVGImageElement | HTMLSourceEl
};
export function renderImageFromUrl(elem: HTMLElement | HTMLImageElement | SVGImageElement | HTMLSourceElement, url: string) {
- if(loadedURLs[url]) return set(elem, url);
+ if(loadedURLs[url]) {
+ set(elem, url);
+ return true;
+ }
if(elem instanceof HTMLSourceElement) {
- return elem.src = url;
+ elem.src = url;
} else {
let loader = new Image();
loader.src = url;
@@ -119,6 +122,8 @@ export function renderImageFromUrl(elem: HTMLElement | HTMLImageElement | SVGIma
loadedURLs[url] = true;
};
}
+
+ return false;
}
export function putPreloader(elem: Element, returnDiv = false) {
diff --git a/src/components/preloader.ts b/src/components/preloader.ts
index 68e66fbb..9063f001 100644
--- a/src/components/preloader.ts
+++ b/src/components/preloader.ts
@@ -49,11 +49,14 @@ export default class ProgressivePreloader {
this.promise = promise;
let tempID = --this.tempID;
- promise.then(() => {
+ let onEnd = () => {
if(tempID == this.tempID) {
this.detach();
}
- });
+
+ promise.notify = null;
+ };
+ promise.then(onEnd, onEnd);
promise.notify = (details: {done: number, total: number}) => {
if(tempID != this.tempID) return;
diff --git a/src/components/scrollable_new.ts b/src/components/scrollable_new.ts
index 80abb276..66b42ba6 100644
--- a/src/components/scrollable_new.ts
+++ b/src/components/scrollable_new.ts
@@ -64,7 +64,7 @@ export default class Scrollable {
private virtualTempIDBottom = 0;
private lastTopID = 0;
private lastBottomID = 0;
- private lastScrollDirection = true; // true = bottom
+ private lastScrollDirection = 0; // true = bottom
private setVisible(element: HTMLElement) {
if(this.visible.has(element)) return;
@@ -116,11 +116,11 @@ export default class Scrollable {
//this.debug && this.log('intersection entry:', entry, isTop, isBottom, this.lastTopID, this.lastBottomID);
});
- if(!filtered.length) {
+ if(!filtered.length || this.lastScrollDirection === 0) {
return;
}
- if(this.lastScrollDirection) { // bottom
+ if(this.lastScrollDirection === 1) { // bottom
let target = filtered[filtered.length - 1].target as HTMLElement;
this.lastBottomID = +target.dataset.virtual;
@@ -300,6 +300,7 @@ export default class Scrollable {
this.disableHoverTimeout = setTimeout(() => {
appendTo.classList.remove('disable-hover');
+ this.lastScrollDirection = 0;
if(!this.measureMutex.isFulfilled) {
this.measureMutex.resolve();
@@ -346,8 +347,12 @@ export default class Scrollable {
}
}
- this.lastScrollDirection = this.lastScrollTop < scrollTop;
- this.lastScrollTop = scrollTop;
+ if(this.lastScrollTop != scrollTop) {
+ this.lastScrollDirection = this.lastScrollTop < scrollTop ? 1 : -1;
+ this.lastScrollTop = scrollTop;
+ } else {
+ this.lastScrollDirection = 0;
+ }
this.onScrollMeasure = 0;
});
}
diff --git a/src/components/wrappers.ts b/src/components/wrappers.ts
index 9599ce18..46ef2c86 100644
--- a/src/components/wrappers.ts
+++ b/src/components/wrappers.ts
@@ -115,6 +115,23 @@ export function wrapVideo({doc, container, message, boxWidth, boxHeight, withTai
let source = document.createElement('source');
video.append(source);
+
+ let span: HTMLSpanElement;
+ if(doc.type != 'round') {
+ span = document.createElement('span');
+ span.classList.add('video-time');
+ container.append(span);
+
+ if(doc.type != 'gif') {
+ span.innerText = (doc.duration + '').toHHMMSS(false);
+
+ let spanPlay = document.createElement('span');
+ spanPlay.classList.add('video-play', 'tgico-largeplay', 'btn-circle', 'position-center');
+ container.append(spanPlay);
+ } else {
+ span.innerText = 'GIF';
+ }
+ }
let loadVideo = () => {
let promise = appDocsManager.downloadDoc(doc);
@@ -133,7 +150,7 @@ export function wrapVideo({doc, container, message, boxWidth, boxHeight, withTai
//return;
- console.log('loaded doc:', doc, doc.url, blob, container);
+ //console.log('loaded doc:', doc, doc.url, blob, container);
renderImageFromUrl(source, doc.url);
source.type = doc.mime_type;
@@ -164,7 +181,7 @@ export function wrapVideo({doc, container, message, boxWidth, boxHeight, withTai
downloadDiv.classList.add('download');
let span = document.createElement('span');
- span.classList.add('tgico-download');
+ span.classList.add('btn-circle', 'tgico-download');
downloadDiv.append(span);
downloadDiv.addEventListener('click', () => {
@@ -172,7 +189,7 @@ export function wrapVideo({doc, container, message, boxWidth, boxHeight, withTai
loadVideo();
});
- container.append(downloadDiv);
+ container.prepend(downloadDiv);
return;
}
@@ -642,7 +659,7 @@ export function wrapPhoto(photoID: string, message: any, container: HTMLDivEleme
}
}
- console.log('wrapPhoto downloaded:', photo, photo.downloaded, container);
+ //console.log('wrapPhoto downloaded:', photo, photo.downloaded, container);
// так нельзя делать, потому что может быть загружен неправильный размер картинки
/* if(photo.downloaded && photo.url) {
@@ -727,6 +744,7 @@ export function wrapSticker(doc: MTDocument, div: HTMLDivElement, middleware?: (
return lazyLoadQueue ? (lazyLoadQueue.push({div, load}), Promise.resolve()) : load();
}
+ let downloaded = doc.downloaded;
let load = () => appDocsManager.downloadDoc(doc.id).then(blob => {
//console.log('loaded sticker:', blob, div);
if(middleware && !middleware()) return;
@@ -803,6 +821,16 @@ export function wrapSticker(doc: MTDocument, div: HTMLDivElement, middleware?: (
} else if(stickerType == 1) {
let img = new Image();
+ if(!downloaded && (!div.firstElementChild || div.firstElementChild.tagName != 'IMG')) {
+ img.style.opacity = '' + 0;
+
+ img.onload = () => {
+ window.requestAnimationFrame(() => {
+ img.style.opacity = '';
+ });
+ };
+ }
+
if(!doc.url) {
appWebpManager.polyfillImage(img, blob).then((url) => {
doc.url = url;
@@ -825,7 +853,7 @@ export function wrapSticker(doc: MTDocument, div: HTMLDivElement, middleware?: (
return lazyLoadQueue && (!doc.downloaded || stickerType == 2) ? (lazyLoadQueue.push({div, load, wasSeen: group == 'chat'}), Promise.resolve()) : load();
}
-export function wrapReply(title: string, subtitle: string, media?: any) {
+export function wrapReply(title: string, subtitle: string, message?: any) {
let div = document.createElement('div');
div.classList.add('reply');
@@ -843,11 +871,15 @@ export function wrapReply(title: string, subtitle: string, media?: any) {
replyTitle.innerHTML = title ? RichTextProcessor.wrapEmojiText(title) : '';
+ let media = message && message.media;
if(media) {
- if(media.photo) {
+ if(message.grouped_id) {
+ replySubtitle.innerHTML = 'Album';
+ } else if(media.photo) {
replySubtitle.innerHTML = 'Photo';
} else if(media.document && media.document.type) {
- replySubtitle.innerHTML = media.document.type;
+ let type = media.document.type as string;
+ replySubtitle.innerHTML = type.charAt(0).toUpperCase() + type.slice(1); // capitalizeFirstLetter
} else if(media.webpage) {
replySubtitle.innerHTML = RichTextProcessor.wrapPlainText(media.webpage.url);
} else {
diff --git a/src/lib/appManagers/apiUpdatesManager.ts b/src/lib/appManagers/apiUpdatesManager.ts
index f7291510..8075b7c7 100644
--- a/src/lib/appManagers/apiUpdatesManager.ts
+++ b/src/lib/appManagers/apiUpdatesManager.ts
@@ -24,8 +24,8 @@ export class ApiUpdatesManager {
};
public channelStates: any = {};
-
public myID = 0;
+ private attached = false;
constructor() {
apiManager.getUserID().then((id) => {
@@ -501,6 +501,9 @@ export class ApiUpdatesManager {
}
public attach() {
+ if(this.attached) return;
+
+ this.attached = true;
apiManager.setUpdatesProcessor(this.processUpdateMessage.bind(this));
apiManager.invokeApi('updates.getState', {}, {noErrorBox: true}).then((stateResult: any) => {
this.updatesState.seq = stateResult.seq;
diff --git a/src/lib/appManagers/appChatsManager.ts b/src/lib/appManagers/appChatsManager.ts
index e3e1551a..f1aa0abc 100644
--- a/src/lib/appManagers/appChatsManager.ts
+++ b/src/lib/appManagers/appChatsManager.ts
@@ -1,4 +1,4 @@
-import { $rootScope, isObject, SearchIndexManager, safeReplaceObject, copy } from "../utils";
+import { $rootScope, isObject, SearchIndexManager, safeReplaceObject, copy, numberWithCommas } from "../utils";
import { RichTextProcessor } from "../richtextprocessor";
import appUsersManager from "./appUsersManager";
@@ -78,6 +78,7 @@ export class AppChatsManager {
}
public getChat(id: number) {
+ if(id < 0) id = -id;
return this.chats[id] || {id: id, deleted: true, access_hash: this.channelAccess[id]};
}
@@ -204,6 +205,14 @@ export class AppChatsManager {
return 'g' + id;
}
+ public getChatMembersString(id: number) {
+ let chat = this.getChat(id);
+
+ let isChannel = this.isChannel(id) && !this.isMegagroup(id);
+ let participants_count = chat.participants_count || chat.participants.participants.length;
+ return numberWithCommas(participants_count) + ' ' + (isChannel ? 'subscribers' : 'members');
+ }
+
public wrapForFull(id: number, fullChat: any) {
var chatFull = copy(fullChat);
var chat = this.getChat(id);
diff --git a/src/lib/appManagers/appDialogsManager.ts b/src/lib/appManagers/appDialogsManager.ts
index caf21825..8b009aec 100644
--- a/src/lib/appManagers/appDialogsManager.ts
+++ b/src/lib/appManagers/appDialogsManager.ts
@@ -1,8 +1,8 @@
import { langPack, findUpClassName, $rootScope, escapeRegExp, whichChild } from "../utils";
import appImManager, { AppImManager } from "./appImManager";
import appPeersManager from './appPeersManager';
-import appMessagesManager, { AppMessagesManager } from "./appMessagesManager";
-import appUsersManager from "./appUsersManager";
+import appMessagesManager, { AppMessagesManager, Dialog } from "./appMessagesManager";
+import appUsersManager, { User } from "./appUsersManager";
import { RichTextProcessor } from "../richtextprocessor";
import { ripple, putPreloader } from "../../components/misc";
//import Scrollable from "../../components/scrollable";
@@ -208,7 +208,7 @@ export class AppDialogsManager {
console.time('getDialogs time');
let loadCount = 50/*this.chatsLoadCount */;
- this.loadDialogsPromise = appMessagesManager.getConversations('', offset, loadCount, +archived);
+ this.loadDialogsPromise = appMessagesManager.getConversations(offset, loadCount, +archived);
let result = await this.loadDialogsPromise;
@@ -535,8 +535,8 @@ export class AppDialogsManager {
let d = [];
d.push(duration % 60 + ' s');
- if(duration > 60) d.push((duration / 60 | 0) + ' min');
- //if(duration > 3600) d.push((duration / 3600 | 0) + ' h');
+ if(duration >= 60) d.push((duration / 60 | 0) + ' min');
+ //if(duration >= 3600) d.push((duration / 3600 | 0) + ' h');
suffix = ' (' + d.reverse().join(' ') + ')';
}
}
@@ -674,22 +674,33 @@ export class AppDialogsManager {
return this.doms[peerID] || this.domsArchived[peerID];
}
- public addDialog(dialog: {
- peerID: number,
- pFlags: any,
- peer: any,
- folder_id?: number
- }, container?: HTMLUListElement, drawStatus = true) {
+ public addDialog(_dialog: Dialog | number, container?: HTMLUListElement, drawStatus = true, rippleEnabled = true, onlyFirstName = false) {
+ let dialog: Dialog;
+
+ if(typeof(_dialog) === 'number') {
+ let originalDialog = appMessagesManager.getDialogByPeerID(_dialog)[0];
+ if(!originalDialog) {
+ originalDialog = {
+ peerID: _dialog,
+ pFlags: {}
+ } as any;
+ }
+
+ dialog = originalDialog;
+ } else {
+ dialog = _dialog;
+ }
+
let peerID: number = dialog.peerID;
if((this.doms[peerID] || this.domsArchived[peerID]) && !container) return;
- let title = appPeersManager.getPeerTitle(peerID);
+ let title = appPeersManager.getPeerTitle(peerID, false, onlyFirstName);
let avatarDiv = document.createElement('div');
avatarDiv.classList.add('user-avatar');
- if(drawStatus && peerID != $rootScope.myID) {
+ if(drawStatus && peerID != $rootScope.myID && dialog.peer) {
let peer = dialog.peer;
switch(peer._) {
@@ -714,7 +725,7 @@ export class AppDialogsManager {
titleSpan.classList.add('user-title');
if(peerID == $rootScope.myID) {
- title = 'Saved Messages';
+ title = onlyFirstName ? 'Saved' : 'Saved Messages';
}
//console.log('trying to load photo for:', title);
@@ -733,21 +744,24 @@ export class AppDialogsManager {
paddingDiv.classList.add('rp');
paddingDiv.append(avatarDiv, captionDiv);
- ripple(paddingDiv, (id) => {
- this.log('dialogs click element');
- this.lastClickID = id;
-
- return new Promise((resolve, reject) => {
- this.rippleCallback = resolve;
- //setTimeout(() => resolve(), 100);
- //window.requestAnimationFrame(() => window.requestAnimationFrame(() => resolve()));
+ if(rippleEnabled) {
+ ripple(paddingDiv, (id) => {
+ this.log('dialogs click element');
+ this.lastClickID = id;
+
+ return new Promise((resolve, reject) => {
+ this.rippleCallback = resolve;
+ //setTimeout(() => resolve(), 100);
+ //window.requestAnimationFrame(() => window.requestAnimationFrame(() => resolve()));
+ });
+ }, (id) => {
+ //console.log('appDialogsManager: ripple onEnd called!');
+ if(id == this.lastGoodClickID) {
+ appImManager.lazyLoadQueue.unlock();
+ }
});
- }, (id) => {
- //console.log('appDialogsManager: ripple onEnd called!');
- if(id == this.lastGoodClickID) {
- appImManager.lazyLoadQueue.unlock();
- }
- });
+ }
+
let li = document.createElement('li');
li.append(paddingDiv);
@@ -807,22 +821,26 @@ export class AppDialogsManager {
return {dom, dialog};
}
- public setTyping(dialog: any, user: any) {
+ public setTyping(dialog: Dialog, user: User) {
let dom = this.getDialogDom(dialog.peerID);
let str = '';
+ if(dialog.peerID < 0) {
+ let s = user.rFirstName || user.username;
+ if(!s) return;
+ str = s + ' ';
+ }
let senderBold = document.createElement('i');
- if(dialog.peerID < 0) str = (user.first_name || user.last_name || user.username) + ' ';
str += 'typing...';
- senderBold.innerText = str;
+ senderBold.innerHTML = str;
dom.lastMessageSpan.innerHTML = '';
dom.lastMessageSpan.append(senderBold);
dom.lastMessageSpan.classList.add('user-typing');
}
- public unsetTyping(dialog: any) {
+ public unsetTyping(dialog: Dialog) {
let dom = this.getDialogDom(dialog.peerID);
dom.lastMessageSpan.classList.remove('user-typing');
this.setLastMessage(dialog, null, dom);
diff --git a/src/lib/appManagers/appImManager.ts b/src/lib/appManagers/appImManager.ts
index 3dc047c0..503b2959 100644
--- a/src/lib/appManagers/appImManager.ts
+++ b/src/lib/appManagers/appImManager.ts
@@ -26,6 +26,7 @@ import Scrollable from '../../components/scrollable_new';
import BubbleGroups from '../../components/bubbleGroups';
import LazyLoadQueue from '../../components/lazyLoadQueue';
import appDocsManager from './appDocsManager';
+import appForward from '../../components/appForward';
console.log('appImManager included!');
@@ -333,6 +334,12 @@ export class AppImManager {
if(!bubble) return;
+ //this.log('chatInner click:', target);
+ if(target.tagName == 'SPAN') {
+ (target.parentElement.querySelector('video') as HTMLElement).click(); // hot-fix for time and play button
+ return;
+ }
+
if((target.tagName == 'IMG' && !target.classList.contains('emoji') && !target.parentElement.classList.contains('user-avatar'))
|| target.tagName == 'image'
|| target.classList.contains('album-item')
@@ -458,7 +465,9 @@ export class AppImManager {
return;
}
- if(e.key == 'Meta' || e.key == 'Control') {
+ if(e.key == 'Escape' && this.peerID != 0) { // hide current dialog
+ this.setPeer(0);
+ } else if(e.key == 'Meta' || e.key == 'Control') {
return;
} else if(e.key == 'c' && (e.ctrlKey || e.metaKey) && target.tagName != 'INPUT') {
return;
@@ -566,14 +575,18 @@ export class AppImManager {
this.contextMenu.querySelector('.menu-reply').addEventListener('click', () => {
let message = appMessagesManager.getMessage(this.contextMenuMsgID);
- this.chatInputC.setTopInfo(appPeersManager.getPeerTitle(message.fromID, true), message.message, undefined, message.media);
+ this.chatInputC.setTopInfo(appPeersManager.getPeerTitle(message.fromID, true), message.message, undefined, message);
this.chatInputC.replyToMsgID = this.contextMenuMsgID;
this.chatInputC.editMsgID = 0;
});
+
+ this.contextMenu.querySelector('.menu-forward').addEventListener('click', () => {
+ appForward.init([this.contextMenuMsgID]);
+ });
this.contextMenuEdit.addEventListener('click', () => {
let message = appMessagesManager.getMessage(this.contextMenuMsgID);
- this.chatInputC.setTopInfo('Editing', message.message, message.message, message.media);
+ this.chatInputC.setTopInfo('Editing', message.message, message.message, message);
this.chatInputC.replyToMsgID = 0;
this.chatInputC.editMsgID = this.contextMenuMsgID;
});
@@ -848,40 +861,11 @@ export class AppImManager {
if(this.myID == this.peerID) {
this.subtitleEl.innerText = appSidebarRight.profileElements.subtitle.innerText = '';
} else if(user && user.status) {
- let subtitle = '';
- switch(user.status._) {
- case 'userStatusRecently': {
- subtitle += 'last seen recently';
- break;
- }
-
- case 'userStatusOffline': {
- subtitle = 'last seen ';
-
- let date = user.status.was_online;
- let now = Date.now() / 1000;
-
- if((now - date) < 60) {
- subtitle += ' just now';
- } else if((now - date) < 3600) {
- subtitle += ((now - date) / 60 | 0) + ' minutes ago';
- } else if(now - date < 86400) {
- subtitle += ((now - date) / 3600 | 0) + ' hours ago';
- } else {
- let d = new Date(date * 1000);
- subtitle += ('0' + d.getDate()).slice(-2) + '.' + ('0' + (d.getMonth() + 1)).slice(-2) + ' at ' +
- ('0' + d.getHours()).slice(-2) + ':' + ('0' + d.getMinutes()).slice(-2);
- }
-
- break;
- }
+ let subtitle = appUsersManager.getUserStatusString(user.id);
- case 'userStatusOnline': {
- this.subtitleEl.classList.add('online');
- appSidebarRight.profileElements.subtitle.classList.add('online');
- subtitle = 'online';
- break;
- }
+ if(subtitle == 'online') {
+ this.subtitleEl.classList.add('online');
+ appSidebarRight.profileElements.subtitle.classList.add('online');
}
appSidebarRight.profileElements.subtitle.innerText = subtitle;
@@ -951,6 +935,10 @@ export class AppImManager {
appSidebarRight.toggleSidebar(false);
this.topbar.style.display = this.chatInput.style.display = this.goDownBtn.style.display = 'none';
this.cleanup();
+ if(appDialogsManager.lastActiveListElement) {
+ appDialogsManager.lastActiveListElement.classList.remove('active');
+ appDialogsManager.lastActiveListElement = null;
+ }
return false;
}
@@ -1087,9 +1075,9 @@ export class AppImManager {
return true;
})/* .catch(err => {
this.log.error(err);
- }) */,
+ }) *//* ,
- appSidebarRight.fillProfileElements()/* ,
+ appSidebarRight.fillProfileElements() *//* ,
appSidebarRight.loadSidebarMedia(true) */
]).catch(err => {
this.log.error('setPeer promises error:', err);
@@ -1152,6 +1140,7 @@ export class AppImManager {
let bubble = this.bubbles[id];
delete this.bubbles[id];
+ this.bubbleGroups.removeBubble(bubble, id);
this.unreadedObserver.unobserve(bubble);
this.scrollable.removeElement(bubble);
//bubble.remove();
@@ -1236,7 +1225,7 @@ 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);
if(message.deleted) return;
else if(message.grouped_id) { // will render only last album's message
let storage = appMessagesManager.groupedMessagesStorage[message.grouped_id];
@@ -1614,7 +1603,7 @@ export class AppImManager {
case 'messageMediaDocument': {
let doc = message.media.document;
- this.log('messageMediaDocument', doc, bubble);
+ //this.log('messageMediaDocument', doc, bubble);
if(doc.sticker/* && doc.size <= 1e6 */) {
bubble.classList.add('sticker');
@@ -1639,7 +1628,7 @@ export class AppImManager {
break;
} else if(doc.type == 'video' || doc.type == 'gif' || doc.type == 'round'/* && doc.size <= 20e6 */) {
- this.log('never get free 2', doc);
+ //this.log('never get free 2', doc);
if(doc.type == 'round') {
bubble.classList.add('round');
@@ -1700,7 +1689,7 @@ export class AppImManager {
}
case 'messageMediaContact': {
- this.log('wrapping contact', message);
+ //this.log('wrapping contact', message);
let contactDiv = document.createElement('div');
contactDiv.classList.add('contact');
@@ -1791,7 +1780,7 @@ export class AppImManager {
bubble.setAttribute('data-original-mid', message.reply_to_mid);
}
- bubbleContainer.append(wrapReply(originalPeerTitle, originalMessage.message || '', originalMessage.media));
+ bubbleContainer.append(wrapReply(originalPeerTitle, originalMessage.message || '', originalMessage));
bubble.classList.add('is-reply');
}
@@ -2033,7 +2022,7 @@ export class AppImManager {
let pageCount = this.bubblesContainer.clientHeight / 38/* * 1.25 */ | 0;
//let loadCount = Object.keys(this.bubbles).length > 0 ? 50 : pageCount;
- let realLoadCount = 50;
+ let realLoadCount = Object.keys(this.bubbles).length > 0 ? 40 : pageCount;//let realLoadCount = 50;
let loadCount = realLoadCount;
if(testScroll) {
@@ -2306,4 +2295,5 @@ export class AppImManager {
}
const appImManager = new AppImManager();
+(window as any).appImManager = appImManager;
export default appImManager;
diff --git a/src/lib/appManagers/appMessagesManager.ts b/src/lib/appManagers/appMessagesManager.ts
index a1eae2f4..07a17001 100644
--- a/src/lib/appManagers/appMessagesManager.ts
+++ b/src/lib/appManagers/appMessagesManager.ts
@@ -38,7 +38,7 @@ export type HistoryResult = {
unreadSkip: boolean
};
-type Dialog = {
+export type Dialog = {
_: 'dialog',
top_message: number,
read_inbox_max_id: number,
@@ -68,9 +68,14 @@ export class AppMessagesManager {
[peerID: string]: HistoryStorage
} = {};
public dialogsStorage: {
- count: any,
- dialogs: Dialog[]
- } = {count: null, dialogs: []};
+ count: number,
+ dialogs: {
+ [folderID: number]: Dialog[]
+ }
+ } = {
+ count: null,
+ dialogs: {}
+ };
public pendingByRandomID: {[randomID: string]: [number, number]} = {};
public pendingByMessageID: any = {};
public pendingAfterMsgs: any = {};
@@ -1137,37 +1142,12 @@ export class AppMessagesManager {
};
}
- public getConversations(query?: string, offsetIndex?: number, limit = 20, folderID = -1) {
- //var curDialogStorage = this.dialogsStorage;
- //var isSearch = typeof(query) == 'string' && query.length;
- let curDialogStorage = this.dialogsStorage.dialogs;
+ public getConversations(offsetIndex?: number, limit = 20, folderID = 0) {
+ let curDialogStorage = this.dialogsStorage.dialogs[folderID] ?? (this.dialogsStorage.dialogs[folderID] = []);
- if(folderID > 0) {
- curDialogStorage = curDialogStorage.filter(d => d.folder_id == folderID);
- } else {
- curDialogStorage = curDialogStorage.filter(d => d.folder_id != 1);
- }
-
- /* if(isSearch) {
- if(!limit || this.cachedResults.query !== query) {
- this.cachedResults.query = query;
-
- var results: any = SearchIndexManager.search(query, this.dialogsIndex);
-
- this.cachedResults.dialogs = [];
- this.dialogsStorage.dialogs.forEach((dialog: any) => {
- if(results[dialog.peerID]) {
- this.cachedResults.dialogs.push(dialog);
- }
- })
- this.cachedResults.count = this.cachedResults.dialogs.length;
- }
- curDialogStorage = this.cachedResults;
- } else { */
- this.cachedResults.query = false;
- //}
+ this.cachedResults.query = false;
- var offset = 0;
+ let offset = 0;
if(offsetIndex > 0) {
for(; offset < curDialogStorage.length; offset++) {
if(offsetIndex > curDialogStorage[offset].index) {
@@ -1176,7 +1156,7 @@ export class AppMessagesManager {
}
}
- if(/* isSearch || */this.allDialogsLoaded[folderID] || curDialogStorage.length >= offset + limit) {
+ if(this.allDialogsLoaded[folderID] || curDialogStorage.length >= offset + limit) {
return Promise.resolve({
dialogs: curDialogStorage.slice(offset, offset + limit),
count: curDialogStorage.length
@@ -1184,17 +1164,11 @@ export class AppMessagesManager {
}
return this.getTopMessages(limit, folderID).then(count => {
- let curDialogStorage = this.dialogsStorage.dialogs;
-
- if(folderID > 0) {
- curDialogStorage = curDialogStorage.filter(d => d.folder_id == folderID);
- } else {
- curDialogStorage = curDialogStorage.filter(d => d.folder_id != 1);
- }
+ let curDialogStorage = this.dialogsStorage.dialogs[folderID];
offset = 0;
if(offsetIndex > 0) {
- for(offset = 0; offset < curDialogStorage.length; offset++) {
+ for(; offset < curDialogStorage.length; offset++) {
if(offsetIndex > curDialogStorage[offset].index) {
break;
}
@@ -1210,20 +1184,14 @@ export class AppMessagesManager {
});
}
- public getTopMessages(limit: number, folderID = -1): Promise {
- var dialogs = this.dialogsStorage.dialogs;
+ public getTopMessages(limit: number, folderID: number): Promise {
+ var dialogs = this.dialogsStorage.dialogs[folderID];
var offsetDate = 0;
var offsetID = 0;
var offsetPeerID = 0;
var offsetIndex = 0;
var flags = 0;
- if(folderID > 0) {
- dialogs = dialogs.filter(d => d.folder_id == folderID);
- } else {
- dialogs = dialogs.filter(d => d.folder_id != 1);
- }
-
if(this.dialogsOffsetDate[folderID]) {
offsetDate = this.dialogsOffsetDate[folderID] + serverTimeManager.serverTimeOffset;
offsetIndex = this.dialogsOffsetDate[folderID] * 0x10000;
@@ -1260,13 +1228,10 @@ export class AppMessagesManager {
var maxSeenIdIncremented = offsetDate ? true : false;
var hasPrepend = false;
- //dialogsResult.dialogs.reverse();
let length = dialogsResult.dialogs.length;
let noIDsDialogs: any = {};
for(let i = length - 1; i >= 0; --i) {
let dialog = dialogsResult.dialogs[i];
- //}
- //dialogsResult.dialogs.forEach((dialog: any) => {
this.saveConversation(dialog);
if(offsetIndex && dialog.index > offsetIndex) {
@@ -1284,8 +1249,6 @@ export class AppMessagesManager {
maxSeenIdIncremented = true;
}
}
- //});
- //dialogsResult.dialogs.reverse();
if(Object.keys(noIDsDialogs).length) {
//setTimeout(() => { // test bad situation
@@ -1315,6 +1278,54 @@ export class AppMessagesManager {
});
}
+ public forwardMessages(peerID: number, mids: number[], options: Partial<{
+ withMyScore: boolean
+ }> = {}) {
+ peerID = AppPeersManager.getPeerMigratedTo(peerID) || peerID;
+ mids = mids.sort((a, b) => a - b);
+
+ var flags = 0;
+ if(options.withMyScore) {
+ flags |= 256;
+ }
+
+ let splitted = appMessagesIDsManager.splitMessageIDsByChannels(mids);
+ let promises: any[] = [];
+
+ for(let channelID in splitted.msgIDs) {
+ let msgIDs = splitted.msgIDs[channelID];
+ let len = msgIDs.length;
+ let randomIDs = [];
+ for(let i = 0; i < len; i++) {
+ randomIDs.push([nextRandomInt(0xFFFFFFFF), nextRandomInt(0xFFFFFFFF)]);
+ }
+
+ let sentRequestOptions: any = {};
+ if(this.pendingAfterMsgs[peerID]) {
+ sentRequestOptions.afterMessageID = this.pendingAfterMsgs[peerID].messageID;
+ }
+
+ let promise = apiManager.invokeApi('messages.forwardMessages', {
+ flags: flags,
+ from_peer: AppPeersManager.getInputPeerByID(-channelID),
+ id: msgIDs,
+ random_id: randomIDs,
+ to_peer: AppPeersManager.getInputPeerByID(peerID)
+ }, sentRequestOptions).then((updates) => {
+ apiUpdatesManager.processUpdateMessage(updates);
+ }, () => {}).then(() => {
+ if(this.pendingAfterMsgs[peerID] === sentRequestOptions) {
+ delete this.pendingAfterMsgs[peerID];
+ }
+ });
+
+ this.pendingAfterMsgs[peerID] = sentRequestOptions;
+ promises.push(promise);
+ }
+
+ return Promise.all(promises);
+ }
+
public generateDialogIndex(date?: any) {
if(date === undefined) {
date = tsNow(true) + serverTimeManager.serverTimeOffset;
@@ -1322,9 +1333,9 @@ export class AppMessagesManager {
return (date * 0x10000) + ((++this.dialogsNum) & 0xFFFF);
}
- public pushDialogToStorage(dialog: any, offsetDate?: number) {
- var dialogs = this.dialogsStorage.dialogs/* .filter(d => d.folder_id == dialog.folder_id) */;
- var pos = this.getDialogByPeerID(dialog.peerID)[1];
+ 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) {
dialogs.splice(pos, 1);
}
@@ -1339,15 +1350,14 @@ export class AppMessagesManager {
this.dialogsOffsetDate[dialog.folder_id] = offsetDate;
}
- var index = dialog.index;
- var i;
- var len = dialogs.length;
+ let index = dialog.index;
+ let len = dialogs.length;
if(!len || index < dialogs[len - 1].index) {
dialogs.push(dialog);
} else if(index >= dialogs[0].index) {
dialogs.unshift(dialog);
} else {
- for(i = 0; i < len; i++) {
+ for(let i = 0; i < len; i++) {
if(index > dialogs[i].index) {
dialogs.splice(i, 0, dialog);
break;
@@ -1377,22 +1387,10 @@ export class AppMessagesManager {
public getDialogByPeerID(peerID: number): [Dialog, number] | [] {
let dialogs = this.dialogsStorage.dialogs;
- let byFolders: {[id: number]: number} = {};
- for(let i = 0, length = dialogs.length; i < length; i++) {
- let dialog = dialogs[i];
- if(!byFolders[dialog.folder_id]) byFolders[dialog.folder_id] = 0;
- byFolders[dialog.folder_id]++;
-
- if(dialog.peerID == peerID) {
- //return [dialog, i];
- let sum = 0;
- for(let id in byFolders) {
- if(+id != dialog.folder_id) {
- sum += byFolders[id];
- }
- }
-
- return [dialog, i - sum];
+ for(let folderID in dialogs) {
+ let index = dialogs[folderID].findIndex(dialog => dialog.peerID == peerID);
+ if(index !== -1) {
+ return [dialogs[folderID][index], index];
}
}
@@ -1627,7 +1625,7 @@ export class AppMessagesManager {
})
}
- public migrateChecks(migrateFrom: any, migrateTo: any) {
+ public migrateChecks(migrateFrom: number, migrateTo: number) {
if(!this.migratedFromTo[migrateFrom] &&
!this.migratedToFrom[migrateTo] &&
appChatsManager.hasChat(-migrateTo)) {
@@ -1641,9 +1639,10 @@ export class AppMessagesManager {
setTimeout(() => {
var foundDialog = this.getDialogByPeerID(migrateFrom);
if(foundDialog.length) {
- this.dialogsStorage.dialogs.splice(foundDialog[1], 1);
+ this.dialogsStorage.dialogs[foundDialog[0].folder_id].splice(foundDialog[1], 1);
$rootScope.$broadcast('dialog_drop', {peerID: migrateFrom});
}
+
$rootScope.$broadcast('dialog_migrate', {migrateFrom: migrateFrom, migrateTo: migrateTo});
}, 100);
}
@@ -1734,7 +1733,7 @@ export class AppMessagesManager {
} else {
var foundDialog = this.getDialogByPeerID(peerID);
if(foundDialog.length) {
- this.dialogsStorage.dialogs.splice(foundDialog[1], 1);
+ this.dialogsStorage.dialogs[foundDialog[0].folder_id].splice(foundDialog[1], 1);
$rootScope.$broadcast('dialog_drop', {peerID: peerID});
}
}
@@ -1853,256 +1852,8 @@ export class AppMessagesManager {
if(channelID && dialog.pts) {
apiUpdatesManager.addChannelState(channelID, dialog.pts);
}
-
- /*if(Config.Modes.packed && !channelID && dialog.unread_count > 0 &&
- this.maxSeenID && dialog.top_message > this.maxSeenID &&
- message.pFlags.unread && !message.pFlags.out) {
- var notifyPeer = message.flags & 16 ? message.from_id : peerID
- NotificationsManager.getPeerMuted(notifyPeer).then((muted: any) => {
- if(!muted) {
- this.notifyAboutMessage(message);
- }
- });
- }*/ // WARNING
}
- /*public handleNotifications() {
- clearTimeout(this.notificationsHandlePromise);
- this.notificationsHandlePromise = 0;
-
- var timeout = $rootScope.idle.isIDLE /* && StatusManager.isOtherDeviceActive() * ? 30000 : 1000;
- Object.keys(this.notificationsToHandle).forEach((key: any) => {
- let notifyPeerToHandle = this.notificationsToHandle[key];
- notifyPeerToHandle.isMutedPromise.then((muted: boolean) => {
- var topMessage = notifyPeerToHandle.top_message
- if(muted ||
- !topMessage.pFlags.unread) {
- return;
- }
-
- setTimeout(() => {
- if(topMessage.pFlags.unread) {
- this.notifyAboutMessage(topMessage, {
- fwd_count: notifyPeerToHandle.fwd_count
- });
- }
- }, timeout);
- });
- });
-
- this.notificationsToHandle = {};
- }*/
-
- /*public notifyAboutMessage(message: any, options: any = {}) {
- var peerID = this.getMessagePeer(message);
- var peerString: string;
- var notification: any = {};
- var notificationMessage = '',
- notificationPhoto;
-
- var notifySettings: any = {}; //NotificationsManager.getNotifySettings(); // warning
-
- if(message.fwdFromID && options.fwd_count) {
- notificationMessage = options.fwd_count;// this.fwdMessagesPluralize(options.fwd_count); // warning
- } else if(message.message) {
- if(notifySettings.nopreview) {
- notificationMessage = 'conversation_message_sent';
- } else {
- notificationMessage = RichTextProcessor.wrapPlainText(message.message);
- }
- } else if(message.media) {
- var captionEmoji = '';
- switch (message.media._) {
- case 'messageMediaPhoto':
- notificationMessage = _('conversation_media_photo_raw');
- captionEmoji = 'рџ–ј';
- break
- case 'messageMediaDocument':
- switch (message.media.document.type) {
- case 'gif':
- notificationMessage = _('conversation_media_gif_raw');
- captionEmoji = 'рџЋ¬'
- break
- case 'sticker':
- notificationMessage = _('conversation_media_sticker');
- var stickerEmoji = message.media.document.stickerEmojiRaw;
- if(stickerEmoji !== undefined) {
- notificationMessage = RichTextProcessor.wrapPlainText(stickerEmoji) + ' ' + notificationMessage;
- }
- break;
- case 'video':
- notificationMessage = _('conversation_media_video_raw');
- captionEmoji = 'рџ“№';
- break;
- case 'round':
- notificationMessage = _('conversation_media_round_raw');
- captionEmoji = 'рџ“№';
- break;
- case 'voice':
- case 'audio':
- notificationMessage = _('conversation_media_audio_raw');
- break;
- default:
- if(message.media.document.file_name) {
- notificationMessage = RichTextProcessor.wrapPlainText('рџ“Ћ ' + message.media.document.file_name);
- } else {
- notificationMessage = _('conversation_media_document_raw');
- captionEmoji = 'рџ“Ћ';
- }
- break;
- }
- break;
-
- case 'messageMediaGeo':
- case 'messageMediaVenue':
- notificationMessage = _('conversation_media_location_raw');
- captionEmoji = 'рџ“Ќ';
- break;
- case 'messageMediaContact':
- notificationMessage = _('conversation_media_contact_raw');
- break;
- case 'messageMediaGame':
- notificationMessage = RichTextProcessor.wrapPlainText('рџЋ® ' + message.media.game.title);
- break;
- case 'messageMediaUnsupported':
- notificationMessage = _('conversation_media_unsupported_raw');
- break;
- default:
- notificationMessage = _('conversation_media_attachment_raw');
- break;
- }
-
- if(captionEmoji != '' &&
- message.media.caption) {
- notificationMessage = RichTextProcessor.wrapPlainText(captionEmoji + ' ' + message.media.caption);
- }
- } else if(message._ == 'messageService') {
- switch(message.action._) {
- case 'messageActionChatCreate':
- notificationMessage = _('conversation_group_created_raw');
- break
- case 'messageActionChatEditTitle':
- notificationMessage = _('conversation_group_renamed_raw');
- break
- case 'messageActionChatEditPhoto':
- notificationMessage = _('conversation_group_photo_updated_raw');
- break
- case 'messageActionChatDeletePhoto':
- notificationMessage = _('conversation_group_photo_removed_raw');
- break
- case 'messageActionChatAddUser':
- case 'messageActionChatAddUsers':
- notificationMessage = _('conversation_invited_user_message_raw');
- break
- case 'messageActionChatReturn':
- notificationMessage = _('conversation_returned_to_group_raw');
- break
- case 'messageActionChatJoined':
- notificationMessage = _('conversation_joined_group_raw');
- break
- case 'messageActionChatDeleteUser':
- notificationMessage = _('conversation_kicked_user_message_raw');
- break
- case 'messageActionChatLeave':
- notificationMessage = _('conversation_left_group_raw');
- break
- case 'messageActionChatJoinedByLink':
- notificationMessage = _('conversation_joined_by_link_raw');
- break
- case 'messageActionChannelCreate':
- notificationMessage = _('conversation_created_channel_raw');
- break
- case 'messageActionChannelEditTitle':
- notificationMessage = _('conversation_changed_channel_name_raw');
- break
- case 'messageActionChannelEditPhoto':
- notificationMessage = _('conversation_changed_channel_photo_raw')
- break
- case 'messageActionChannelDeletePhoto':
- notificationMessage = _('conversation_removed_channel_photo_raw')
- break
- case 'messageActionPinMessage':
- notificationMessage = _('conversation_pinned_message_raw')
- break
- case 'messageActionGameScore':
- notificationMessage = message.action.score;//this.gameScorePluralize(message.action.score); // warning
- break
-
- case 'messageActionPhoneCall':
- switch(message.action.type) {
- case 'out_missed':
- notificationMessage = _('message_service_phonecall_canceled_raw')
- break
- case 'in_missed':
- notificationMessage = _('message_service_phonecall_missed_raw')
- break
- case 'out_ok':
- notificationMessage = _('message_service_phonecall_outgoing_raw')
- break
- case 'in_ok':
- notificationMessage = _('message_service_phonecall_incoming_raw')
- break
- }
- break
- }
- }
-
- if(peerID > 0) {
- var fromUser = appUsersManager.getUser(message.from_id);
- var fromPhoto = appUsersManager.getUserPhoto(message.from_id);
-
- notification.title = (fromUser.first_name || '') +
- (fromUser.first_name && fromUser.last_name ? ' ' : '') +
- (fromUser.last_name || '')
- if(!notification.title) {
- notification.title = fromUser.phone || _('conversation_unknown_user_raw')
- }
-
- notificationPhoto = fromPhoto
-
- peerString = appUsersManager.getUserString(peerID)
- } else {
- notification.title = appChatsManager.getChat(-peerID).title || _('conversation_unknown_chat_raw')
-
- if(message.from_id > 0) {
- var fromUser = appUsersManager.getUser(message.from_id)
- notification.title = (fromUser.first_name || fromUser.last_name || _('conversation_unknown_user_raw')) +
- ' @ ' +
- notification.title
- }
-
- notificationPhoto = appChatsManager.getChatPhoto(-peerID)
-
- peerString = appChatsManager.getChatString(-peerID)
- }
-
- notification.title = RichTextProcessor.wrapPlainText(notification.title)
-
- notification.onclick = function () {
- $rootScope.$broadcast('history_focus', {
- peerString: peerString,
- messageID: message.flags & 16 ? message.mid : 0
- })
- }
-
- notification.message = notificationMessage
- notification.key = 'msg' + message.mid
- notification.tag = peerString
- notification.silent = message.pFlags.silent || false
-
- if(notificationPhoto.location && !notificationPhoto.location.empty) {
- apiFileManager.downloadSmallFile(notificationPhoto.location/* , notificationPhoto.size *)
- .then((blob) => {
- if(message.pFlags.unread) {
- notification.image = blob
- // NotificationsManager.notify(notification) // warning
- }
- })
- } else {
- // NotificationsManager.notify(notification) // warning
- }
- }*/
-
public mergeReplyKeyboard(historyStorage: any, message: any) {
// console.log('merge', message.mid, message.reply_markup, historyStorage.reply_markup)
if(!message.reply_markup &&
@@ -2516,15 +2267,6 @@ export class AppMessagesManager {
}
return false;
- /* if(foundDialog) {
- // console.log('done read history', peerID)
- foundDialog.unread_count = 0
- $rootScope.$broadcast('dialog_unread', {peerID: peerID, count: 0})
- $rootScope.$broadcast('messages_read')
- if(historyStorage && historyStorage.history.length) {
- foundDialog.read_inbox_max_id = historyStorage.history[0]
- }
- } */
}).finally(() => {
delete historyStorage.readPromise;
});
@@ -2593,12 +2335,12 @@ export class AppMessagesManager {
}
public handleUpdate(update: any) {
- console.log('AMM: handleUpdate:', update._);
+ //console.log('AMM: handleUpdate:', update._);
switch(update._) {
case 'updateMessageID': {
var randomID = update.random_id;
var pendingData = this.pendingByRandomID[randomID];
- console.log('AMM updateMessageID:', update, pendingData);
+ //console.log('AMM updateMessageID:', update, pendingData);
if(pendingData) {
var peerID: number = pendingData[0];
var tempID = pendingData[1];
@@ -2720,36 +2462,10 @@ export class AppMessagesManager {
this.newDialogsHandlePromise = window.setTimeout(this.handleNewDialogs.bind(this), 0);
}
- /*if(inboxUnread &&
- ($rootScope.selectedPeerID != peerID || $rootScope.idle.isIDLE)) {
- var notifyPeer = message.flags & 16 ? message.from_id : peerID;
- var notifyPeerToHandle = this.notificationsToHandle[notifyPeer];
- if(notifyPeerToHandle === undefined) {
- notifyPeerToHandle = this.notificationsToHandle[notifyPeer] = {
- isMutedPromise: Promise.resolve()/* NotificationsManager.getPeerMuted(notifyPeer), // WARNING
- fwd_count: 0,
- from_id: 0
- };
- }
-
- if(notifyPeerToHandle.from_id != message.from_id) {
- notifyPeerToHandle.from_id = message.from_id;
- notifyPeerToHandle.fwd_count = 0;
- }
- if(message.fwdFromID) {
- notifyPeerToHandle.fwd_count++;
- }
-
- notifyPeerToHandle.top_message = message;
-
- if(!this.notificationsHandlePromise) {
- this.notificationsHandlePromise = window.setTimeout(this.handleNotifications.bind(this), 1000);
- }
- } */
break;
}
- case 'updateDialogPinned': {
+ /* case 'updateDialogPinned': {
var peerID = AppPeersManager.getPeerID(update.peer);
var foundDialog = this.getDialogByPeerID(peerID);
@@ -2826,7 +2542,7 @@ export class AppMessagesManager {
}
})
break;
- }
+ } */
case 'updateEditMessage':
case 'updateEditChannelMessage': {
@@ -3092,37 +2808,41 @@ export class AppMessagesManager {
delete this.historiesStorage[peerID];
$rootScope.$broadcast('history_forbidden', peerID);
}
+
if(hasDialog != needDialog) {
if(needDialog) {
this.reloadConversation(-channelID);
} else {
if(foundDialog[0]) {
- this.dialogsStorage.dialogs.splice(foundDialog[1], 1);
+ this.dialogsStorage.dialogs[foundDialog[0].folder_id].splice(foundDialog[1], 1);
$rootScope.$broadcast('dialog_drop', {peerID: peerID});
}
}
}
+
break;
}
case 'updateChannelReload': {
- var channelID: number = update.channel_id;
- var peerID = -channelID;
- var foundDialog = this.getDialogByPeerID(peerID);
+ let channelID: number = update.channel_id;
+ let peerID = -channelID;
+ let foundDialog = this.getDialogByPeerID(peerID);
if(foundDialog[0]) {
- this.dialogsStorage.dialogs.splice(foundDialog[1], 1);
+ this.dialogsStorage.dialogs[foundDialog[0].folder_id].splice(foundDialog[1], 1);
}
+
delete this.historiesStorage[peerID];
this.reloadConversation(-channelID).then(() => {
$rootScope.$broadcast('history_reload', peerID);
});
+
break;
}
case 'updateChannelMessageViews': {
- var views = update.views;
- var mid = appMessagesIDsManager.getFullMessageID(update.id, update.channel_id);
- var message = this.getMessage(mid);
+ let views = update.views;
+ let mid = appMessagesIDsManager.getFullMessageID(update.id, update.channel_id);
+ let message = this.getMessage(mid);
if(message && message.views && message.views < views) {
message.views = views;
$rootScope.$broadcast('message_views', {
@@ -3567,57 +3287,6 @@ export class AppMessagesManager {
});
}
- /* public wrapForDialog(msgID: number, dialog?: any) {
- var useCache = msgID && dialog !== undefined;
- var unreadCount = dialog && dialog.unread_count;
-
- if(useCache && this.messagesForDialogs[msgID] !== undefined) {
- delete this.messagesForDialogs[msgID].typing;
- this.messagesForDialogs[msgID].unreadCount = unreadCount;
- return this.messagesForDialogs[msgID];
- }
-
- var message = copy(this.messagesStorage[msgID]);
-
- if(!message || !message.to_id) {
- if(dialog && dialog.peerID) {
- message = {
- _: 'message',
- to_id: AppPeersManager.getOutputPeer(dialog.peerID),
- deleted: true,
- date: tsNow(true),
- pFlags: {out: true}
- }
- } else {
- return message;
- }
- }
-
- message.peerID = this.getMessagePeer(message);
- message.peerData = AppPeersManager.getPeer(message.peerID);
- message.peerString = AppPeersManager.getPeerString(message.peerID);
- message.unreadCount = unreadCount;
- message.index = dialog && dialog.index || (message.date * 0x10000);
- message.pinned = dialog && dialog.pFlags.pinned || false;
-
- if(message._ == 'messageService' && message.action.user_id) {
- message.action.user = appUsersManager.getUser(message.action.user_id);
- }
-
- if(message.message && message.message.length) {
- message.richMessage = RichTextProcessor.wrapRichText(message.message.substr(0, 128), {noLinks: true, noLinebreaks: true});
- }
-
- message.dateText = message.date; //dateOrTimeFilter(message.date); // warning
-
- if(useCache) {
- message.draft = '';//DraftsManager.getServerDraft(message.peerID); // warning
- this.messagesForDialogs[msgID] = message;
- }
-
- return message;
- } */
-
public fetchSingleMessages() {
if(this.fetchSingleMessagesPromise) {
return this.fetchSingleMessagesPromise;
diff --git a/src/lib/appManagers/appPeersManager.ts b/src/lib/appManagers/appPeersManager.ts
index 665cbfb8..b757793d 100644
--- a/src/lib/appManagers/appPeersManager.ts
+++ b/src/lib/appManagers/appPeersManager.ts
@@ -37,7 +37,7 @@ const AppPeersManager = {
return false;
},
- getPeerTitle: (peerID: number | any, plainText = false) => {
+ getPeerTitle: (peerID: number | any, plainText = false, onlyFirstName = false) => {
let peer: any = {};
if(!isObject(peerID)) {
peer = AppPeersManager.getPeer(peerID);
@@ -53,6 +53,10 @@ const AppPeersManager = {
} else {
title = peer.title;
}
+
+ if(onlyFirstName) {
+ title = title.split(' ')[0];
+ }
return plainText ? title : RichTextProcessor.wrapEmojiText(title);
},
diff --git a/src/lib/appManagers/appPhotosManager.ts b/src/lib/appManagers/appPhotosManager.ts
index c5e066e2..ec90604c 100644
--- a/src/lib/appManagers/appPhotosManager.ts
+++ b/src/lib/appManagers/appPhotosManager.ts
@@ -27,6 +27,12 @@ export class AppPhotosManager {
private photos: {
[id: string]: MTPhoto
} = {};
+ private documentThumbsCache: {
+ [docID: string]: {
+ downloaded: number,
+ url: string
+ }
+ } = {};
public windowW = 0;
public windowH = 0;
@@ -243,7 +249,7 @@ export class AppPhotosManager {
element.setAttributeNS(null, 'width', '' + w);
element.setAttributeNS(null, 'height', '' + h);
- console.log('set dimensions to svg element:', element, w, h);
+ //console.log('set dimensions to svg element:', element, w, h);
if(element.firstElementChild) {
let imageSvg = element.firstElementChild as SVGImageElement;
@@ -268,7 +274,10 @@ export class AppPhotosManager {
photoSize = this.choosePhotoSize(photo, fullWidth, fullHeight);
}
- if(photo.downloaded >= photoSize.size && photo.url) {
+ let isDocument = photo._ == 'document';
+ let cacheContext = isDocument ? (this.documentThumbsCache[photo.id] ?? (this.documentThumbsCache[photo.id] = {downloaded: -1, url: ''})) : photo;
+
+ if(cacheContext.downloaded >= photoSize.size && cacheContext.url) {
return Promise.resolve();
}
@@ -280,7 +289,7 @@ export class AppPhotosManager {
// maybe it's a thumb
let isPhoto = photoSize.size && photo.access_hash && photo.file_reference;
let location = isPhoto ? {
- _: photo._ == 'document' ? 'inputDocumentFileLocation' : 'inputPhotoFileLocation',
+ _: isDocument ? 'inputDocumentFileLocation' : 'inputPhotoFileLocation',
id: photo.id,
access_hash: photo.access_hash,
file_reference: photo.file_reference,
@@ -296,20 +305,21 @@ export class AppPhotosManager {
promise = apiFileManager.downloadSmallFile(location);
}
- if(typeof(photoID) === 'string') {
- let photo = this.photos[photoID];
- promise.then(blob => {
- if(!photo.downloaded || photo.downloaded < blob.size) {
- photo.downloaded = blob.size;
- photo.url = URL.createObjectURL(blob);
+ promise.then(blob => {
+ if(!cacheContext.downloaded || cacheContext.downloaded < blob.size) {
+ cacheContext.downloaded = blob.size;
+ cacheContext.url = URL.createObjectURL(blob);
- console.log('wrote photo:', photo, photoSize, blob);
- }
- });
- }
+ //console.log('wrote photo:', photo, photoSize, cacheContext, blob);
+ }
+ });
return promise;
}
+
+ public getDocumentCachedThumb(docID: string) {
+ return this.documentThumbsCache[docID];
+ }
public getPhoto(photoID: any): MTPhoto {
return isObject(photoID) ? photoID : this.photos[photoID];
diff --git a/src/lib/appManagers/appSidebarLeft.ts b/src/lib/appManagers/appSidebarLeft.ts
index 8e994643..1256e798 100644
--- a/src/lib/appManagers/appSidebarLeft.ts
+++ b/src/lib/appManagers/appSidebarLeft.ts
@@ -5,6 +5,16 @@ 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 appUsersManager from "./appUsersManager";
+import Scrollable from "../../components/scrollable_new";
+import appPhotosManager from "./appPhotosManager";
+import { appPeersManager } from "../services";
+
+const SLIDERITEMSIDS = {
+ archived: 1,
+ contacts: 2
+};
class AppSidebarLeft {
private sidebarEl = document.getElementById('column-left') as HTMLDivElement;
@@ -16,18 +26,36 @@ class AppSidebarLeft {
private menuEl = this.toolsBtn.querySelector('.btn-menu');
private savedBtn = this.menuEl.querySelector('.menu-saved');
private archivedBtn = this.menuEl.querySelector('.menu-archive');
+ private contactsBtn = this.menuEl.querySelector('.menu-contacts');
private logOutBtn = this.menuEl.querySelector('.menu-logout');
public archivedCount = this.archivedBtn.querySelector('.archived-count') as HTMLSpanElement;
//private log = logger('SL');
- private globalSearch = new AppSearch(this.searchContainer, this.searchInput, {
+ private searchGroups = {
contacts: new SearchGroup('Contacts and Chats', 'contacts'),
globalContacts: new SearchGroup('Global Search', 'contacts'),
- messages: new SearchGroup('Global Search', 'messages')
- });
+ messages: new SearchGroup('Global Search', 'messages'),
+ people: new SearchGroup('People', 'contacts', false, 'search-group-people'),
+ recent: new SearchGroup('Recent', 'contacts', false, 'search-group-recent')
+ };
+ 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;
+ private contactsInput: HTMLInputElement;
constructor() {
+ let peopleContainer = document.createElement('div');
+ peopleContainer.classList.add('search-group-scrollable');
+ peopleContainer.append(this.searchGroups.people.list);
+ this.searchGroups.people.container.append(peopleContainer);
+ let peopleScrollable = new Scrollable(peopleContainer, 'x');
+
this.savedBtn.addEventListener('click', (e) => {
///////this.log('savedbtn click');
setTimeout(() => { // menu doesn't close if no timeout (lol)
@@ -37,11 +65,12 @@ class AppSidebarLeft {
});
this.archivedBtn.addEventListener('click', (e) => {
- appDialogsManager.chatsArchivedContainer.classList.add('active');
- this.toolsBtn.classList.remove('active');
- this.backBtn.classList.add('active');
- //this.toolsBtn.classList.remove('tgico-menu', 'btn-menu-toggle');
- //this.toolsBtn.classList.add('tgico-back');
+ this.selectTab(SLIDERITEMSIDS.archived);
+ });
+
+ this.contactsBtn.addEventListener('click', (e) => {
+ this.openContacts();
+ this.selectTab(SLIDERITEMSIDS.contacts);
});
this.logOutBtn.addEventListener('click', (e) => {
@@ -51,6 +80,8 @@ class AppSidebarLeft {
this.searchInput.addEventListener('focus', (e) => {
this.toolsBtn.classList.remove('active');
this.backBtn.classList.add('active');
+ this.searchContainer.classList.remove('hide');
+ void this.searchContainer.offsetWidth; // reflow
this.searchContainer.classList.add('active');
/* if(!this.globalSearch.searchInput.value) {
@@ -59,11 +90,10 @@ class AppSidebarLeft {
}
} */
- this.searchInput.addEventListener('blur', (e) => {
+ false && this.searchInput.addEventListener('blur', (e) => {
if(!this.searchInput.value) {
this.toolsBtn.classList.add('active');
this.backBtn.classList.remove('active');
- this.searchContainer.classList.remove('active');
this.backBtn.click();
}
@@ -78,16 +108,129 @@ class AppSidebarLeft {
this.toolsBtn.classList.add('active');
this.backBtn.classList.remove('active');
this.searchContainer.classList.remove('active');
- this.globalSearch.reset();
+
+ setTimeout(() => {
+ this.searchContainer.classList.add('hide');
+ this.globalSearch.reset();
+
+ this.searchGroups.people.setActive();
+ //this.searchGroups.recent.setActive();
+ }, 150);
});
$rootScope.$on('dialogs_archived_unread', (e: CustomEvent) => {
this.archivedCount.innerText = '' + e.detail.count;
});
- /* appUsersManager.getTopPeers().then(categories => {
- this.log('got top categories:', categories);
- }); */
+ this._selectTab = horizontalMenu(null, this.sidebarEl.querySelector('.sidebar-slider') as HTMLDivElement, null, null, 420);
+ this._selectTab(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);
+ });
+ });
+
+ appUsersManager.getTopPeers().then(categories => {
+ console.log('got top categories:', categories);
+
+ let category = categories[0];
+ if(!category || !category.peers) {
+ return;
+ }
+
+ category.peers.forEach((topPeer: {
+ _: 'topPeer',
+ peer: any,
+ rating: number
+ }) => {
+ let peerID = appPeersManager.getPeerID(topPeer.peer);
+ let {dialog, dom} = appDialogsManager.addDialog(peerID, this.searchGroups.people.list, false, true, true);
+
+ 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' ? `${status}` : 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);
}
}
diff --git a/src/lib/appManagers/appSidebarRight.ts b/src/lib/appManagers/appSidebarRight.ts
index 2fa63a7d..46584cde 100644
--- a/src/lib/appManagers/appSidebarRight.ts
+++ b/src/lib/appManagers/appSidebarRight.ts
@@ -59,12 +59,6 @@ class AppSidebarRight {
private sharedMediaSelected: HTMLDivElement = null;
private lazyLoadQueueSidebar = new LazyLoadQueue(5);
- /* public minMediaID: {
- [type: string]: number
- } = {}; */
- public cleared: {
- [type: string]: boolean
- } = {};
public historiesStorage: {
[peerID: number]: {
@@ -121,7 +115,7 @@ class AppSidebarRight {
this.scroll.scrollTop -= this.profileTabs.offsetTop;
}
- this.log('setVirtualContainer', id, this.sharedMediaSelected);
+ this.log('setVirtualContainer', id, this.sharedMediaSelected, this.sharedMediaSelected.childElementCount);
this.scroll.setVirtualContainer(this.sharedMediaSelected);
if(this.prevTabID != -1 && !this.sharedMediaSelected.childElementCount) { // quick brown fix
@@ -132,7 +126,10 @@ class AppSidebarRight {
this.prevTabID = id;
this.scroll.onScroll();
- }, this.onSidebarScroll.bind(this));
+ }, () => {
+ this.onSidebarScroll.bind(this);
+ this.scroll.onScroll();
+ });
let sidebarCloseBtn = this.sidebarEl.querySelector('.sidebar-close-button') as HTMLButtonElement;
sidebarCloseBtn.addEventListener('click', () => {
@@ -212,24 +209,18 @@ class AppSidebarRight {
this.sidebarEl.classList.toggle('active');
}
-
- public performSearchResult(ids: number[], type: string) {
- let peerID = this.peerID;
- let sharedMediaDiv: HTMLDivElement;
+ public filterMessagesByType(ids: number[], type: string) {
let messages: any[] = [];
for(let mid of ids) {
let message = appMessagesManager.getMessage(mid);
if(message.media) messages.push(message);
}
-
- let elemsToAppend: HTMLElement[] = [];
-
- // https://core.telegram.org/type/MessagesFilter
+
+ let filtered: any[] = [];
+
switch(type) {
case 'inputMessagesFilterPhotoVideo': {
- sharedMediaDiv = this.sharedMedia.contentMedia;
-
for(let message of messages) {
let media = message.media.photo || message.media.document || (message.media.webpage && message.media.webpage.document);
if(!media) {
@@ -241,14 +232,85 @@ class AppSidebarRight {
//this.log('broken video', media);
continue;
}
+
+ filtered.push(message);
+ }
+
+ break;
+ }
+
+ case 'inputMessagesFilterDocument': {
+ for(let message of messages) {
+ if(!message.media.document || message.media.document.type == 'voice' || message.media.document.type == 'audio') {
+ continue;
+ }
+
+ let doc = message.media.document;
+ if(doc.attributes) {
+ if(doc.attributes.find((a: any) => a._ == "documentAttributeSticker")) {
+ continue;
+ }
+ }
+
+ filtered.push(message);
+ }
+ break;
+ }
+
+ case 'inputMessagesFilterUrl': {
+ for(let message of messages) {
+ if(!message.media.webpage || message.media.webpage._ == 'webPageEmpty') {
+ continue;
+ }
+ filtered.push(message);
+ }
+
+ break;
+ }
+
+ case 'inputMessagesFilterMusic': {
+ for(let message of messages) {
+ if(!message.media.document || message.media.document.type != 'audio') {
+ continue;
+ }
+
+ filtered.push(message);
+ }
+
+ break;
+ }
+
+ default:
+ break;
+ }
+
+ return filtered;
+ }
+
+ public performSearchResult(messages: any[], type: string) {
+ let peerID = this.peerID;
+
+ let sharedMediaDiv: HTMLDivElement;
+
+ let elemsToAppend: HTMLElement[] = [];
+
+ // https://core.telegram.org/type/MessagesFilter
+ switch(type) {
+ case 'inputMessagesFilterPhotoVideo': {
+ sharedMediaDiv = this.sharedMedia.contentMedia;
+
+ for(let message of messages) {
+ let media = message.media.photo || message.media.document || (message.media.webpage && message.media.webpage.document);
+
let div = document.createElement('div');
//console.log(message, photo);
let isPhoto = media._ == 'photo';
let photo = isPhoto ? appPhotosManager.getPhoto(media.id) : null;
- if(!photo || !photo.downloaded) {
+ let isDownloaded = (photo && photo.downloaded) || appPhotosManager.getDocumentCachedThumb(media.id);
+ if(!isDownloaded) {
//this.log('inputMessagesFilterPhotoVideo', message, media, photo, div);
let sizes = media.sizes || media.thumbs;
@@ -260,33 +322,39 @@ class AppSidebarRight {
}
//this.log('inputMessagesFilterPhotoVideo', message, media);
+
+ if(!isPhoto) {
+ let span = document.createElement('span');
+ span.classList.add('video-time');
+ div.append(span);
+
+ if(media.type != 'gif') {
+ span.innerText = (media.duration + '').toHHMMSS(false);
+
+ /* let spanPlay = document.createElement('span');
+ spanPlay.classList.add('video-play', 'tgico-largeplay', 'btn-circle', 'position-center');
+ div.append(spanPlay); */
+ } else {
+ span.innerText = 'GIF';
+ }
+ }
let load = () => appPhotosManager.preloadPhoto(isPhoto ? media.id : media, appPhotosManager.choosePhotoSize(media, 200, 200))
- .then((blob) => {
+ .then(() => {
if($rootScope.selectedPeerID != peerID) {
this.log.warn('peer changed');
return;
}
- if(photo && photo.url) {
- renderImageFromUrl(div, photo.url);
- } else {
- let url = URL.createObjectURL(blob);
- this.urlsToRevoke.push(url);
-
- let img = new Image();
- img.src = url;
- img.onload = () => {
- div.style.backgroundImage = 'url(' + url + ')';
- };
+ let url = (photo && photo.url) || appPhotosManager.getDocumentCachedThumb(media.id).url;
+ if(url) {
+ renderImageFromUrl(div, url);
}
-
- //div.style.backgroundImage = 'url(' + url + ')';
});
div.dataset.mid = '' + message.mid;
- if(photo && photo.downloaded) load();
+ if(isDownloaded) load();
else this.lazyLoadQueueSidebar.push({div, load});
this.lastSharedMediaDiv.append(div);
@@ -310,19 +378,6 @@ class AppSidebarRight {
sharedMediaDiv = this.sharedMedia.contentDocuments;
for(let message of messages) {
- if(!message.media.document || message.media.document.type == 'voice' || message.media.document.type == 'audio') {
- continue;
- }
-
- let doc = message.media.document;
- if(doc.attributes) {
- if(doc.attributes.find((a: any) => a._ == "documentAttributeSticker")) {
- continue;
- }
- }
-
- //this.log('come back down to my knees', message);
-
let div = wrapDocument(message.media.document, true);
elemsToAppend.push(div);
}
@@ -333,10 +388,6 @@ class AppSidebarRight {
sharedMediaDiv = this.sharedMedia.contentLinks;
for(let message of messages) {
- if(!message.media.webpage || message.media.webpage._ == 'webPageEmpty') {
- continue;
- }
-
let webpage = message.media.webpage;
let div = document.createElement('div');
@@ -391,10 +442,6 @@ class AppSidebarRight {
sharedMediaDiv = this.sharedMedia.contentAudio;
for(let message of messages) {
- if(!message.media.document || message.media.document.type != 'audio') {
- continue;
- }
-
let div = wrapAudio(message.media.document, true);
elemsToAppend.push(div);
}
@@ -412,8 +459,10 @@ class AppSidebarRight {
if(elemsToAppend.length) {
//window.requestAnimationFrame(() => {
- elemsToAppend.forEach(el => this.scroll.append(el, false));
+ //elemsToAppend.forEach(el => this.scroll.append(el, false));
//});
+
+ sharedMediaDiv.append(...elemsToAppend);
}
if(sharedMediaDiv) {
@@ -439,6 +488,8 @@ class AppSidebarRight {
let typesToLoad = single ? [this.sharedMediaType] : this.sharedMediaTypes;
typesToLoad = typesToLoad.filter(type => !this.loadedAllMedia[type]);
if(!typesToLoad.length) return;
+
+ let loadCount = (appPhotosManager.windowH / 130 | 0) * 3; // that's good for all types
let historyStorage = this.historiesStorage[peerID] ?? (this.historiesStorage[peerID] = {});
@@ -447,19 +498,35 @@ class AppSidebarRight {
let history = historyStorage[type] ?? (historyStorage[type] = []);
- let loadCount = (appPhotosManager.windowH / 130 | 0) * 3;
-
// render from cache
- if(history.length && this.usedFromHistory[type] < history.length && this.cleared[type]) {
- let ids = history.slice(this.usedFromHistory[type], this.usedFromHistory[type] + loadCount);
- this.log('loadSidebarMedia: will render from cache', this.usedFromHistory[type], history, ids, loadCount);
- this.usedFromHistory[type] += ids.length;
- this.performSearchResult(ids, type);
+ if(history.length && this.usedFromHistory[type] < history.length) {
+ let messages: any[] = [];
+ let used = this.usedFromHistory[type];
+
+ do {
+ let ids = history.slice(used, used + loadCount);
+ this.log('loadSidebarMedia: will render from cache', used, history, ids, loadCount);
+ used += ids.length;
+
+ messages.push(...this.filterMessagesByType(ids, type));
+ } while(messages.length < loadCount && used < history.length);
+
+ // если перебор
+ if(messages.length > loadCount) {
+ let diff = messages.length - loadCount;
+ messages = messages.slice(0, messages.length - diff);
+ used -= diff;
+ }
+
+ this.usedFromHistory[type] = used;
+ if(messages.length) {
+ this.performSearchResult(messages, type);
+ }
+
return Promise.resolve();
}
// заливать новую картинку сюда только после полной отправки!
- //let maxID = this.minMediaID[type] || 0;
let maxID = history[history.length - 1] || 0;
let ids = !maxID && appMessagesManager.historiesStorage[peerID]
@@ -474,7 +541,7 @@ class AppSidebarRight {
ids = ids.concat(value.history);
history.push(...ids);
- this.log('loadSidebarMedia: search house of glass', type, value, ids, this.cleared);
+ this.log('loadSidebarMedia: search house of glass', type, value, ids);
if($rootScope.selectedPeerID != peerID) {
this.log.warn('peer changed');
@@ -484,14 +551,11 @@ class AppSidebarRight {
if(value.history.length < loadCount) {
this.loadedAllMedia[type] = true;
}
-
- if(this.cleared[type]) {
- //ids = history;
- delete this.cleared[type];
- }
+
+ this.usedFromHistory[type] = history.length;
if(ids.length) {
- this.performSearchResult(ids, type);
+ this.performSearchResult(this.filterMessagesByType(ids, type), type);
}
}, (err) => {
this.log.error('load error:', err);
@@ -550,8 +614,6 @@ class AppSidebarRight {
this.urlsToRevoke.length = 0;
this.sharedMediaTypes.forEach(type => {
- //this.minMediaID[type] = 0;
- this.cleared[type] = true;
this.usedFromHistory[type] = 0;
});
diff --git a/src/lib/appManagers/appUsersManager.ts b/src/lib/appManagers/appUsersManager.ts
index 8d668bbf..62194a5f 100644
--- a/src/lib/appManagers/appUsersManager.ts
+++ b/src/lib/appManagers/appUsersManager.ts
@@ -5,14 +5,41 @@ import appChatsManager from "./appChatsManager";
import apiManager from '../mtproto/mtprotoworker';
import serverTimeManager from "../mtproto/serverTimeManager";
+export type User = {
+ _: 'user',
+ access_hash: string,
+ first_name: string,
+ last_name: string,
+ username: string,
+ flags: number,
+ id: number,
+ phone: string,
+ photo: any,
+
+ status?: Partial<{
+ _: 'userStatusOffline' | 'userStatusOnline' | 'userStatusRecently' | 'userStatusLastWeek' | 'userStatusLastMonth' | 'userStatusEmpty',
+ wasStatus: any,
+ was_online: number,
+ expires: number
+ }>,
+
+ initials?: string,
+ num?: number,
+ pFlags: Partial<{verified: boolean, support: boolean, self: boolean, bot: boolean, min: number, deleted: boolean}>,
+ rFirstName?: string,
+ rFullName?: string,
+ sortName?: string,
+ sortStatus?: number,
+};
+
export class AppUsersManager {
- public users: any = {};
- public usernames: any = {};
- public userAccess: {[x: number]: string} = {};
+ public users: {[userID: number]: User} = {};
+ public usernames: {[username: string]: number} = {};
+ public userAccess: {[userID: number]: string} = {};
public cachedPhotoLocations: any = {};
public contactsIndex = SearchIndexManager.createIndex();
- public contactsFillPromise: any;
- public contactsList: any;
+ public contactsFillPromise: Promise;
+ public contactsList: number[];
public myID: number;
constructor() {
@@ -80,28 +107,27 @@ export class AppUsersManager {
});
}
- /* public fillContacts () {
+ public fillContacts() {
if(this.contactsFillPromise) {
return this.contactsFillPromise;
}
- return this.contactsFillPromise = MTProto.apiManager.invokeApi('contacts.getContacts', {
+ return this.contactsFillPromise = apiManager.invokeApi('contacts.getContacts', {
hash: 0
}).then((result: any) => {
- var userID, searchText;
- var i;
+ var userID;
this.contactsList = [];
this.saveApiUsers(result.users);
- for(var i = 0; i < result.contacts.length; i++) {
- userID = result.contacts[i].user_id
+ result.contacts.forEach((contact: any) => {
+ userID = contact.user_id;
this.contactsList.push(userID);
- //SearchIndexManager.indexObject(userID, getUserSearchText(userID), contactsIndex); WARNING
- }
+ SearchIndexManager.indexObject(userID, this.getUserSearchText(userID), this.contactsIndex);
+ });
return this.contactsList;
- })
- } */
+ });
+ }
public getUserSearchText(id: number) {
var user = this.users[id];
@@ -120,32 +146,28 @@ export class AppUsersManager {
' ' + serviceText;
}
- /* function getContacts (query) {
- return fillContacts().then(function (contactsList) {
- if (angular.isString(query) && query.length) {
- var results = SearchIndexManager.search(query, contactsIndex)
- var filteredContactsList = []
+ public getContacts(query?: string) {
+ return this.fillContacts().then(contactsList => {
+ if(query) {
+ const results: any = SearchIndexManager.search(query, this.contactsIndex);
+ const filteredContactsList = contactsList.filter(id => !!results[id]);
- for (var i = 0; i < contactsList.length; i++) {
- if (results[contactsList[i]]) {
- filteredContactsList.push(contactsList[i])
- }
- }
- contactsList = filteredContactsList
+ contactsList = filteredContactsList;
+ }
+
+ contactsList.sort((userID1: number, userID2: number) => {
+ const sortName1 = (this.users[userID1] || {}).sortName || '';
+ const sortName2 = (this.users[userID2] || {}).sortName || '';
+ if(sortName1 == sortName2) {
+ return 0;
}
- contactsList.sort(function (userID1, userID2) {
- var sortName1 = (users[userID1] || {}.sortName) || ''
- var sortName2 = (users[userID2] || {}.sortName) || ''
- if (sortName1 == sortName2) {
- return 0
- }
- return sortName1 > sortName2 ? 1 : -1
- })
+ return sortName1 > sortName2 ? 1 : -1;
+ });
- return contactsList
- })
- } */
+ return contactsList;
+ });
+ }
public resolveUsername(username: string) {
return this.usernames[username] || 0;
@@ -235,7 +257,7 @@ export class AppUsersManager {
this.userAccess[id] = accessHash;
}
- public getUserStatusForSort(status: any) {
+ public getUserStatusForSort(status: User['status']) {
if(status) {
var expires = status.expires || status.was_online;
if(expires) {
@@ -255,17 +277,77 @@ export class AppUsersManager {
return 0;
}
- public getUser(id: any) {
+ public getUser(id: any): User {
if(isObject(id)) {
return id;
}
- return this.users[id] || {id: id, deleted: true, num: 1, access_hash: this.userAccess[id]};
+
+ return this.users[id] || {id: id, pFlags: {deleted: true}, num: 1, access_hash: this.userAccess[id]} as User;
}
public getSelf() {
return this.getUser(this.myID);
}
+ public getUserStatusString(userID: number) {
+ if(this.isBot(userID)) {
+ return 'bot';
+ }
+
+ let user = this.getUser(userID);
+ if(!user || !user.status) {
+ return '';
+ }
+
+ let str = '';
+ switch(user.status._) {
+ case 'userStatusRecently': {
+ str = 'last seen recently';
+ break;
+ }
+
+ case 'userStatusLastWeek': {
+ str = 'last seen last week';
+ break;
+ }
+
+ case 'userStatusLastMonth': {
+ str = 'last seen last month';
+ break;
+ }
+
+ case 'userStatusOffline': {
+ str = 'last seen ';
+
+ let date = user.status.was_online;
+ let now = Date.now() / 1000;
+
+ if((now - date) < 60) {
+ str += ' just now';
+ } else if((now - date) < 3600) {
+ let c = (now - date) / 60 | 0;
+ str += c + ' ' + (c == 1 ? 'minute' : 'minutes') + ' ago';
+ } else if(now - date < 86400) {
+ let c = (now - date) / 3600 | 0;
+ str += c + ' ' + (c == 1 ? 'hour' : 'hours') + ' ago';
+ } else {
+ let d = new Date(date * 1000);
+ str += ('0' + d.getDate()).slice(-2) + '.' + ('0' + (d.getMonth() + 1)).slice(-2) + ' at ' +
+ ('0' + d.getHours()).slice(-2) + ':' + ('0' + d.getMinutes()).slice(-2);
+ }
+
+ break;
+ }
+
+ case 'userStatusOnline': {
+ str = 'online';
+ break;
+ }
+ }
+
+ return str;
+ }
+
public isBot(id: number) {
return this.users[id] && this.users[id].pFlags.bot;
}
@@ -345,12 +427,6 @@ export class AppUsersManager {
}
}
- public wrapForFull(id: number) {
- var user = this.getUser(id);
-
- return user;
- }
-
/* function importContact (phone, firstName, lastName) {
return MtpApiManager.invokeApi('contacts.importContacts', {
contacts: [{
@@ -426,7 +502,7 @@ export class AppUsersManager {
flags: 1,
correspondents: true,
offset: 0,
- limit: 5,
+ limit: 30,
hash: 0,
}).then((peers: any) => {
//console.log(peers);
@@ -477,7 +553,7 @@ export class AppUsersManager {
var user = this.users[userID];
if(user) {
- var status = offline ? {
+ var status: any = offline ? {
_: 'userStatusOffline',
was_online: tsNow(true)
} : {
diff --git a/src/lib/bin_utils.ts b/src/lib/bin_utils.ts
index 69ccbabf..bc4c4a61 100644
--- a/src/lib/bin_utils.ts
+++ b/src/lib/bin_utils.ts
@@ -66,7 +66,7 @@ export function bytesFromHex(hexString: string) {
return bytes;
}
-export function bytesToBase64(bytes: number[]) {
+export function bytesToBase64(bytes: number[] | Uint8Array) {
var mod3
var result = ''
diff --git a/src/lib/lottieLoader.ts b/src/lib/lottieLoader.ts
index cf28e1fd..c73cb8e9 100644
--- a/src/lib/lottieLoader.ts
+++ b/src/lib/lottieLoader.ts
@@ -20,7 +20,7 @@ class LottieLoader {
public loadLottie() {
if(this.loaded) return this.loaded;
- this.loaded = new Promise((resolve, reject) => {
+ return this.loaded = new Promise((resolve, reject) => {
(window as any).lottieLoaded = () => {
console.log('lottie loaded');
this.lottie = (window as any).lottie;
diff --git a/src/lib/mtproto/apiManager.ts b/src/lib/mtproto/apiManager.ts
index cf3171b7..2db4525b 100644
--- a/src/lib/mtproto/apiManager.ts
+++ b/src/lib/mtproto/apiManager.ts
@@ -59,6 +59,10 @@ export class ApiManager {
$rootScope.$broadcast('user_auth', fullUserAuth);
/// #endif
}
+
+ public setBaseDcID(dcID: number) {
+ this.baseDcID = dcID;
+ }
// mtpLogOut
public async logOut() {
@@ -68,9 +72,7 @@ export class ApiManager {
for(let dcID = 1; dcID <= 5; dcID++) {
storageKeys.push(prefix + dcID + '_auth_key');
- //storageKeys.push('dc' + dcID + '_auth_keyID');
- //storageKeys.push('t_dc' + dcID + '_auth_key');
- //storageKeys.push('t_dc' + dcID + '_auth_keyID');
+ //storageKeys.push(prefix + dcID + '_auth_keyID');
}
// WebPushApiManager.forceUnsubscribe(); // WARNING
@@ -95,9 +97,9 @@ export class ApiManager {
error.handled = true;
this.telegramMeNotify(false);
this.mtpClearStorage();
- }).then(() => {
+ })/* .then(() => {
location.pathname = '/';
- });
+ }) */;
}
public mtpClearStorage() {
diff --git a/src/lib/mtproto/mtproto.worker.js b/src/lib/mtproto/mtproto.worker.js
index abcd31a7..3baf0efd 100644
--- a/src/lib/mtproto/mtproto.worker.js
+++ b/src/lib/mtproto/mtproto.worker.js
@@ -26,15 +26,26 @@ ctx.onmessage = function(e) {
ctx.postMessage({taskID: taskID, result: result});
});
- default:
- return apiManager[e.data.task].apply(apiManager, e.data.args).then(result => {
- //console.log(e.data.task + ' result:', result, taskID);
- ctx.postMessage({taskID: taskID, result: result});
- }).catch(err => {
- //console.error(e.data.task + ' err:', err, taskID);
+ default: {
+ try {
+ let result = apiManager[e.data.task].apply(apiManager, e.data.args);
+ if(result instanceof Promise) {
+ result.then(result => {
+ //console.log(e.data.task + ' result:', result, taskID);
+ ctx.postMessage({taskID: taskID, result: result});
+ }).catch(err => {
+ //console.error(e.data.task + ' err:', err, taskID);
+ ctx.postMessage({taskID: taskID, error: err});
+ });
+ } else {
+ ctx.postMessage({taskID: taskID, result: result});
+ }
+ } catch(err) {
ctx.postMessage({taskID: taskID, error: err});
- });
+ }
+
//throw new Error('Unknown task: ' + e.data.task);
+ }
}
}
diff --git a/src/lib/mtproto/mtprotoworker.ts b/src/lib/mtproto/mtprotoworker.ts
index 22faa125..80a1d4f1 100644
--- a/src/lib/mtproto/mtprotoworker.ts
+++ b/src/lib/mtproto/mtprotoworker.ts
@@ -125,6 +125,10 @@ class ApiManagerProxy extends CryptoWorkerMethods {
return this.performTaskWorker('invokeApi', method, params, options);
}
+ public setBaseDcID(dcID: number) {
+ return this.performTaskWorker('setBaseDcID', dcID);
+ }
+
public setUserAuth(userAuth: {id: number}) {
$rootScope.$broadcast('user_auth', userAuth);
return this.performTaskWorker('setUserAuth', userAuth);
diff --git a/src/lib/mtproto/serverTimeManager.ts b/src/lib/mtproto/serverTimeManager.ts
index a4cd89fa..cb569bc9 100644
--- a/src/lib/mtproto/serverTimeManager.ts
+++ b/src/lib/mtproto/serverTimeManager.ts
@@ -8,7 +8,7 @@ export class ServerTimeManager {
public midnightOffset = this.midnightNoOffset - (Math.floor(+this.midnightOffseted / 1000));
- public serverTimeOffset = 0;
+ public serverTimeOffset = 0; // in seconds
public timeParams = {
midnightOffset: this.midnightOffset,
serverTimeOffset: this.serverTimeOffset
diff --git a/src/pages/pageSignQR.ts b/src/pages/pageSignQR.ts
new file mode 100644
index 00000000..da828a64
--- /dev/null
+++ b/src/pages/pageSignQR.ts
@@ -0,0 +1,158 @@
+//import apiManager from '../lib/mtproto/apiManager';
+import apiManager from '../lib/mtproto/mtprotoworker';
+import Page from './page';
+import pageIm from './pageIm';
+import pagePassword from './pagePassword';
+import { App } from '../lib/mtproto/mtproto_config';
+import { bytesToBase64, bytesCmp } from '../lib/bin_utils';
+import serverTimeManager from '../lib/mtproto/serverTimeManager';
+import { User } from '../lib/appManagers/appUsersManager';
+
+/* interface Authorization {
+ _: 'authorization',
+ flags: number,
+ pFlags: Partial<{current: true, official_app: true, password_pending: true}>,
+ hash: number[],
+ device_model: string,
+ platform: string,
+ system_version: string,
+ api_id: number,
+ app_name: string,
+ app_version: string,
+ date_created: number,
+ date_active: number,
+ ip: string,
+ country: string,
+ region: string
+}; */
+
+interface AuthAuthorization {
+ flags: number,
+ pFlags: Partial<{tmp_sessions: number}>,
+ user: User
+}
+
+interface LoginToken {
+ _: 'auth.loginToken',
+ expires: number,
+ token: Uint8Array
+};
+
+interface LoginTokenMigrateTo {
+ _: 'auth.loginTokenMigrateTo',
+ dc_id: number,
+ token: Uint8Array
+};
+
+interface LoginTokenSuccess {
+ _: 'auth.loginTokenSuccess',
+ authorization: AuthAuthorization
+};
+
+let onFirstMount = async() => {
+ const pageElement = page.pageEl;
+ const imageDiv = pageElement.querySelector('.auth-image') as HTMLDivElement;
+
+ const results = await Promise.all([
+ import('qr-code-styling' as any)
+ ]);
+ const QRCodeStyling = results[0].default;
+
+ let stop = false;
+ document.addEventListener('user_auth', () => {
+ stop = true;
+ }, {once: true});
+
+ let options: {dcID?: number} = {};
+ let prevToken: Uint8Array;
+
+ do {
+ if(stop) {
+ break;
+ }
+
+ try {
+ let loginToken: LoginToken | LoginTokenMigrateTo | LoginTokenSuccess = await apiManager.invokeApi('auth.exportLoginToken', {
+ api_id: App.id,
+ api_hash: App.hash,
+ except_ids: []
+ }/* , options */);
+
+ if(loginToken._ == 'auth.loginTokenMigrateTo') {
+ if(!options.dcID) {
+ options.dcID = loginToken.dc_id;
+ apiManager.setBaseDcID(loginToken.dc_id);
+ //continue;
+ }
+
+ loginToken = await apiManager.invokeApi('auth.importLoginToken', {
+ token: loginToken.token
+ }, options) as LoginToken;
+ }
+
+ if(loginToken._ == 'auth.loginTokenSuccess') {
+ let authorization = loginToken.authorization;
+ apiManager.setUserAuth({
+ id: authorization.user.id
+ });
+ pageIm.mount();
+ break;
+ }
+
+ /* // to base64
+ var decoder = new TextDecoder('utf8');
+ var b64encoded = btoa(String.fromCharCode.apply(null, [...loginToken.token])); */
+
+ if(!prevToken || !bytesCmp(prevToken, loginToken.token)) {
+ prevToken = loginToken.token;
+
+ let encoded = bytesToBase64(loginToken.token);
+ let url = "tg://login?token=" + encoded.replace(/\+/g, "-").replace(/\//g, "_").replace(/\=+$/, "");
+
+ imageDiv.innerHTML = '';
+ const qrCode = new QRCodeStyling({
+ width: 166,
+ height: 166,
+ data: url,
+ image: "assets/img/logo_padded.svg",
+ dotsOptions: {
+ color: "#000000",
+ type: "rounded"
+ },
+ imageOptions: {
+ imageSize: .75
+ },
+ backgroundOptions: {
+ color: "#ffffff"
+ },
+ qrOptions: {
+ errorCorrectionLevel: "L"
+ }
+ });
+ qrCode.append(imageDiv);
+ }
+
+ let timestamp = Date.now() / 1000;
+ let diff = loginToken.expires - timestamp - serverTimeManager.serverTimeOffset;
+
+ await new Promise((resolve, reject) => setTimeout(resolve, diff > 5 ? 5e3 : 1e3 * diff | 0));
+ } catch(err) {
+ switch(err.type) {
+ case 'SESSION_PASSWORD_NEEDED':
+ console.warn('pageSignQR: SESSION_PASSWORD_NEEDED');
+ err.handled = true;
+ pagePassword.mount();
+ break;
+ default:
+ console.error('pageSignQR: default error:', err);
+ break;
+ }
+ }
+ } while(true);
+};
+
+const page = new Page('page-signQR', true, () => {
+ onFirstMount();
+});
+
+export default page;
diff --git a/src/pages/pageSignUp.ts b/src/pages/pageSignUp.ts
index 0bf671e4..3e3eb526 100644
--- a/src/pages/pageSignUp.ts
+++ b/src/pages/pageSignUp.ts
@@ -5,7 +5,6 @@ import pageIm from './pageIm';
import apiManager from '../lib/mtproto/mtprotoworker';
import apiFileManager from '../lib/mtproto/apiFileManager';
import Page from './page';
-import { calcImageInBox } from '../lib/utils';
let authCode: {
'phone_number': string,
diff --git a/src/scss/components/_global.scss b/src/scss/components/_global.scss
index 965253d2..22fa24d3 100644
--- a/src/scss/components/_global.scss
+++ b/src/scss/components/_global.scss
@@ -32,13 +32,6 @@ a {
align-items: center;
}
-
-// classic clearfix
-.clearfix {
- clear: both;
-}
-
-
// Z-levels
.z-depth-0 {
box-shadow: none !important;
@@ -82,54 +75,6 @@ a {
0 11px 15px -7px rgba(0,0,0,0.2);
}
-.hoverable {
- transition: box-shadow .25s;
-
- &:hover {
- box-shadow: 0 8px 17px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19);
- }
-}
-
-// Icon Styles
-
-i {
- line-height: inherit;
-
- &.left {
- float: left;
- margin-right: 15px;
- }
- &.right {
- float: right;
- margin-left: 15px;
- }
- &.tiny {
- font-size: 1rem;
- }
- &.small {
- font-size: 2rem;
- }
- &.medium {
- font-size: 4rem;
- }
- &.large {
- font-size: 6rem;
- }
-}
-
-/*********************
-Transition Classes
-**********************/
-
-ul.staggered-list li {
- opacity: 0;
-}
-
-.fade-in {
- opacity: 0;
- transform-origin: 0 50%;
-}
-
/*******************
Utility Classes
*******************/
@@ -138,29 +83,11 @@ Utility Classes
display: none !important;
}
-// Text Align
-.left-align {
- text-align: left;
-}
-.right-align {
- text-align: right
-}
-.center, .center-align {
- text-align: center;
-}
-
-.left {
- float: left !important;
-}
-.right {
- float: right !important;
-}
-
// No Text Select
.no-select {
user-select: none;
}
-.circle {
- border-radius: 50%;
+.center-align {
+ text-align: center;
}
diff --git a/src/scss/components/_typography.scss b/src/scss/components/_typography.scss
index 2a94424f..97a4be83 100644
--- a/src/scss/components/_typography.scss
+++ b/src/scss/components/_typography.scss
@@ -1,39 +1,16 @@
-
a {
text-decoration: none;
}
-html{
+html {
line-height: 1.5;
- /* @media only screen and (min-width: 0) {
- font-size: 14px;
- }
-
- @media only screen and (min-width: $medium-screen) {
- font-size: 14.5px;
- }
-
- @media only screen and (min-width: $large-screen) {
- font-size: 15px;
- } */
-
font-weight: normal;
}
h1, h2, h3, h4, h5, h6 {
- font-weight: 400;
line-height: 1.3;
}
-// Header Styles
-h1 a, h2 a, h3 a, h4 a, h5 a, h6 a { font-weight: inherit; }
-/* h1 { font-size: $h1-fontsize; line-height: 110%; margin: ($h1-fontsize / 1.5) 0 ($h1-fontsize / 2.5) 0;}
-h2 { font-size: $h2-fontsize; line-height: 110%; margin: ($h2-fontsize / 1.5) 0 ($h2-fontsize / 2.5) 0;}
-h3 { font-size: $h3-fontsize; line-height: 110%; margin: ($h3-fontsize / 1.5) 0 ($h3-fontsize / 2.5) 0;}
-h4 { font-size: $h4-fontsize; line-height: 110%; margin: ($h4-fontsize / 1.5) 0 ($h4-fontsize / 2.5) 0;}
-h5 { font-size: $h5-fontsize; line-height: 110%; margin: ($h5-fontsize / 1.5) 0 ($h5-fontsize / 2.5) 0;}
-h6 { font-size: $h6-fontsize; line-height: 110%; margin: ($h6-fontsize / 1.5) 0 ($h6-fontsize / 2.5) 0;} */
-
// Text Styles
em { font-style: italic; }
strong { font-weight: 500; }
diff --git a/src/scss/partials/_chat.scss b/src/scss/partials/_chat.scss
index 64b0fc00..6434f800 100644
--- a/src/scss/partials/_chat.scss
+++ b/src/scss/partials/_chat.scss
@@ -1,4 +1,5 @@
$chat-max-width: 696px;
+$time-background: rgba(0, 0, 0, 0.35);
#bubble-contextmenu > div {
padding: 0 84px 0 16px;
@@ -75,6 +76,10 @@ $chat-max-width: 696px;
.chat-more-button {
margin-left: 8px;
+
+ .btn-menu {
+ top: calc(100% + 7px);
+ }
}
.chat-info {
@@ -134,7 +139,7 @@ $chat-max-width: 696px;
/* position: absolute;
bottom: 0;
left: 0; */
- position: relative;
+ //position: relative; // неизвестно зачем это было
//display: flex; // for end
//flex-direction: unset;
@@ -163,6 +168,10 @@ $chat-max-width: 696px;
width: 50px;
height: 50px;
}
+
+ &-container .preloader-circular {
+ background-color: $time-background;
+ }
}
#bubbles-inner {
@@ -185,6 +194,8 @@ $chat-max-width: 696px;
}
&.is-channel:not(.is-chat) {
+ padding-bottom: 55px;
+
.bubble__container {
max-width: 100%;
}
@@ -250,9 +261,11 @@ $chat-max-width: 696px;
.bubble {
padding-top: 5px;
- display: grid;
+ /* display: grid;
grid-template-columns: 1fr $chat-max-width 1fr;
- grid-row-gap: 0px;
+ grid-row-gap: 0px; */
+ max-width: $chat-max-width;
+ margin: 0 auto;
&.is-date {
position: -webkit-sticky;
@@ -268,10 +281,10 @@ $chat-max-width: 696px;
}
}
- &:before, &:after {
+ /* &:before, &:after {
content: " ";
width: 100%;
- }
+ } */
&__container {
//min-width: 60px;
@@ -303,7 +316,8 @@ $chat-max-width: 696px;
padding: 5px 0;
.bubble__container {
- justify-self: center;
+ /* justify-self: center; */
+ margin: 0 auto;
max-width: 100%;
}
}
@@ -536,6 +550,8 @@ $chat-max-width: 696px;
height: auto;
max-width: 100%;
cursor: pointer;
+ opacity: 1;
+ transition: opacity .3s ease;
}
.download {
@@ -550,15 +566,15 @@ $chat-max-width: 696px;
align-items: center;
span {
- width: 54px;
- height: 54px;
- line-height: 54px;
- background-color: rgba(0, 0, 0, .7);
- border-radius: 50%;
+ background-color: $time-background;
font-size: 23px;
color: #fff;
text-align: center;
}
+
+ & ~ .video-play {
+ display: none;
+ }
}
}
@@ -894,7 +910,7 @@ $chat-max-width: 696px;
bottom: .1rem;
right: .2rem;
border-radius: 12px;
- background-color: rgba(0, 0, 0, .4);
+ background-color: $time-background;
padding: 0 .2rem;
z-index: 2;
@@ -948,6 +964,32 @@ $chat-max-width: 696px;
}
}
+ span.video-time {
+ position: absolute;
+ top: 3px;
+ left: 3px;
+ border-radius: 12px;
+ background-color: $time-background;
+ padding: 0px 6px 0px 6px;
+ z-index: 2;
+ font-size: 12px;
+ color: white;
+ display: flex;
+ align-items: center;
+ cursor: pointer;
+ user-select: none;
+ -webkit-user-select: none;
+ }
+
+ span.video-play {
+ background-color: $time-background;
+ color: #fff;
+ text-align: center;
+ font-size: 34px;
+ line-height: 60px;
+ cursor: pointer;
+ }
+
&.is-edited.channel-post .time {
min-width: calc(5rem + 46px);
}
@@ -998,6 +1040,10 @@ $chat-max-width: 696px;
}
}
}
+
+ &:not(.webpage):not(.is-album):not(.sticker):not(.round) .attachment, .album-item {
+ background-color: #000;
+ }
&.hide-name:not(.is-reply):not(.is-message-empty) .message {
//padding-top: .2675rem;
@@ -1141,6 +1187,22 @@ $chat-max-width: 696px;
.audio-subtitle, .contact-number, .audio-time {
color: #707579 !important;
}
+
+ .message.audio-message {
+ .media-progress {
+ &__seek {
+ background: rgba(193, 207, 220, 0.39);
+ }
+
+ &__filled {
+ background-color: #0089ff;
+ }
+
+ input::-webkit-slider-thumb {
+ background: #63a2e3;
+ }
+ }
+ }
}
.is-out {
diff --git a/src/scss/partials/_chatlist.scss b/src/scss/partials/_chatlist.scss
index f33ff7d4..d121cd98 100644
--- a/src/scss/partials/_chatlist.scss
+++ b/src/scss/partials/_chatlist.scss
@@ -262,46 +262,57 @@
font-weight: 500;
}
- &:not(.search-group-messages) {
- .user-avatar {
- width: 48px;
- height: 48px;
- }
- }
-
&-contacts {
padding: 16px 0 7px;
- li {
- //margin-bottom: 2px;
- padding-bottom: 4px;
- padding-top: 2px;
- }
-
- li > .rp {
- padding: 9px 11.5px !important;
- height: 66px;
- }
-
.search-group__name {
padding-bottom: 17px;
}
+ }
+ }
+}
- .user-caption {
- padding: 1px 3.5px 1px 13px;
- }
+// use together like class="chats-container contacts-container"
+.contacts-container, .search-group-contacts {
+ .user-avatar {
+ width: 48px;
+ height: 48px;
+ }
- .user-title, b, .user-last-message b {
- font-weight: normal;
- }
+ li {
+ //margin-bottom: 2px;
+ padding-bottom: 4px;
+ padding-top: 2px;
+ }
- p {
- height: 24px;
- }
+ li > .rp {
+ padding: 9px 11.5px !important;
+ height: 66px;
+ }
- span.user-last-message {
- font-size: 14px;
- }
- }
+ .user-caption {
+ padding: 1px 3.5px 1px 13px;
+ }
+
+ .user-title, b, .user-last-message b {
+ font-weight: normal;
+ }
+
+ p {
+ height: 24px;
+ }
+
+ span.user-last-message {
+ font-size: 14px;
+ }
+}
+
+#contacts-container {
+ .sidebar-header {
+ margin-bottom: 1px;
+ }
+
+ .input-search {
+ margin-left: 16px;
}
}
diff --git a/src/scss/partials/_emojiDropdown.scss b/src/scss/partials/_emojiDropdown.scss
index ef497597..c25f0da4 100644
--- a/src/scss/partials/_emojiDropdown.scss
+++ b/src/scss/partials/_emojiDropdown.scss
@@ -49,42 +49,57 @@
.tabs-container {
/* width: 300%; */
height: 100%;
+
+ .category-title {
+ position: sticky;
+ top: 0;
+ font-size: .85rem;
+ color: $color-gray;
+ background: linear-gradient(to bottom,#fff 0,rgba(255,255,255,.9) 60%,rgba(255,255,255,0) 100%);
+ z-index: 2;
+ padding: .53333rem 6PX .66667rem;
+ width: 100%;
+ }
.emoji-category {
- font-size: 2.25rem;
- line-height: 2.25rem;
padding-top: 1px;
+ position: relative;
- display: grid;
- grid-column-gap: 2.44px;
- grid-template-columns: 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr;
+ .category-items {
+ display: grid;
+ grid-column-gap: 2.44px;
+ grid-template-columns: 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr;
+
+ font-size: 2.25rem;
+ line-height: 2.25rem;
+
+ > * {
+ margin: 0;
+ padding: 4px 4px;
+ line-height: inherit;
+ border-radius: 8px;
+ cursor: pointer;
+ user-select: none;
+ -webkit-user-select: none;
+
+ width: 42px;
+ height: 42px;
+
+ .emoji {
+ width: 100%;
+ height: 100%;
+ }
+
+ &:hover {
+ background-color: rgba(112, 117, 121, 0.08);
+ }
+ }
+ }
&:first-child {
padding-top: 5px;
}
-
- > * {
- margin: 0;
- padding: 4px 4px;
- line-height: inherit;
- border-radius: 8px;
- cursor: pointer;
- user-select: none;
- -webkit-user-select: none;
-
- width: 42px;
- height: 42px;
-
- .emoji {
- width: 100%;
- height: 100%;
- }
-
- &:hover {
- background-color: rgba(112, 117, 121, 0.08);
- }
- }
-
+
/* &::after {
content: "";
flex: auto;
@@ -92,58 +107,53 @@
}
.sticker-category {
- display: flex;
- align-items: center;
- justify-content: space-between;
- flex-wrap: wrap;
+ position: relative;
&::after {
content: "";
flex: auto;
}
- }
-
- .sticker-category {
+
/* &.not-full::after {
content: "";
flex: auto;
} */
- > div {
+ .category-items {
width: 100%;
display: flex;
align-items: center;
justify-content: space-between;
flex-wrap: wrap;
- }
-
- > div > div {
- width: 80px;
- height: 80px;
- display: flex;
- align-items: center;
- /* overflow: hidden; */
- cursor: pointer;
- user-select: none;
- -webkit-user-select: none;
- /* margin: 3.5px 0;
- margin-right: 6.25px; */
- padding: 1px 2.5px;
- justify-content: center;
- border-radius: 12px;
- padding: 0;
-
- &:hover {
- background-color: rgba(112, 117, 121, 0.08);
- }
-
- /* &:nth-child(5n+5) {
- margin-right: 0;
- } */
-
- > * {
- max-width: 100%;
- max-height: 100%;
+
+ > div {
+ width: 80px;
+ height: 80px;
+ display: flex;
+ align-items: center;
+ /* overflow: hidden; */
+ cursor: pointer;
+ user-select: none;
+ -webkit-user-select: none;
+ /* margin: 3.5px 0;
+ margin-right: 6.25px; */
+ padding: 1px 2.5px;
+ justify-content: center;
+ border-radius: 12px;
+ padding: 0;
+
+ &:hover {
+ background-color: rgba(112, 117, 121, 0.08);
+ }
+
+ /* &:nth-child(5n+5) {
+ margin-right: 0;
+ } */
+
+ > * {
+ max-width: 100%;
+ max-height: 100%;
+ }
}
}
}
diff --git a/src/scss/partials/_ico.scss b/src/scss/partials/_ico.scss
index 1095cb61..fb119e03 100644
--- a/src/scss/partials/_ico.scss
+++ b/src/scss/partials/_ico.scss
@@ -4,3 +4,4 @@ $tgico-font-path: "../../assets/fonts" !default;
$tgico-check: "\e900";
$tgico-checks: "\e95a";
$tgico-sending: "\e919";
+$tgico-close: "\e943";
diff --git a/src/scss/partials/_leftSidebar.scss b/src/scss/partials/_leftSidebar.scss
index 50da26d0..c69a28e3 100644
--- a/src/scss/partials/_leftSidebar.scss
+++ b/src/scss/partials/_leftSidebar.scss
@@ -8,6 +8,10 @@
position: relative;
}
+ .sidebar-slider {
+ height: 100%;
+ }
+
.sidebar-header__btn-container {
position: relative;
width: 39.75px;
@@ -67,5 +71,70 @@
}
}
-
+ .search-group-people {
+ ul {
+ display: flex;
+ flex-direction: row;
+ padding-left: 4px;
+ margin-top: -1px;
+ padding-bottom: 1px;
+ }
+
+ li {
+ margin-right: 5px;
+ padding: 0;
+ }
+
+ .rp {
+ height: 98px;
+ max-height: 98px;
+ border-radius: 10px;
+ max-width: 78px;
+ width: 78px;
+ align-items: center;
+ position: relative;
+ display: flex;
+ flex-direction: column;
+ cursor: pointer;
+ padding: 12px 0 0 !important;
+ overflow: hidden;
+ margin: 0;
+ }
+
+ .user-avatar {
+ width: 54px;
+ height: 54px;
+ }
+
+ .user-caption {
+ max-width: 65px;
+ padding: 2px 0px 9px;
+ font-size: 12px;
+ }
+
+ .user-title {
+ max-width: unset;
+ }
+
+ .search-group-scrollable {
+ position: relative;
+
+ > .scrollable {
+ position: relative;
+ }
+ }
+ }
+}
+
+#search-container {
+ transition: 150ms ease-in-out opacity,150ms ease-in-out transform;
+ transform: scale(1.1, 1.1);
+ opacity: 0;
+ display: flex;
+
+ &.active {
+ transform: scale(1, 1);
+ transform-origin: center;
+ opacity: 1;
+ }
}
diff --git a/src/scss/partials/_mediaViewer.scss b/src/scss/partials/_mediaViewer.scss
index 26b9da80..2952aa16 100644
--- a/src/scss/partials/_mediaViewer.scss
+++ b/src/scss/partials/_mediaViewer.scss
@@ -122,12 +122,12 @@
.media-viewer-caption {
flex: 1;
text-align: center;
- display: flex;
- flex-direction: column;
- justify-content: center;
- align-items: center;
color: $darkgrey;
transition: .2s;
+ max-width: 50vw;
+ word-break: break-word;
+ overflow: hidden;
+ text-overflow: ellipsis;
&:hover {
color: #fff;
diff --git a/src/scss/partials/_rightSIdebar.scss b/src/scss/partials/_rightSIdebar.scss
index b04dad76..54df6e9f 100644
--- a/src/scss/partials/_rightSIdebar.scss
+++ b/src/scss/partials/_rightSIdebar.scss
@@ -36,6 +36,14 @@
flex: 1 1 auto;
}
}
+
+ .sidebar-search {
+ display: none;
+
+ &.active {
+ display: flex;
+ }
+ }
}
.profile {
@@ -201,7 +209,7 @@
width: 100%;
display: flex;
flex-direction: column;
- padding-top: 4px;
+ padding: 4px 7.5px 7.5px;
> div {
display: grid;
@@ -220,9 +228,8 @@
background-position: center center;
display: flex;
- //background-color: #000;
- justify-content: center;
- align-items: center;
+ background-color: #000;
+ position: relative;
&::before {
content: "";
@@ -233,6 +240,28 @@
}
}
}
+
+ span.video-time {
+ position: relative;
+ left: 5px;
+ top: 4px;
+ height: 18px;
+ border-radius: 4px;
+ background-color: $time-background;
+ padding: 0px 6px 0px 5px;
+ z-index: 2;
+ font-size: 12px;
+ color: white;
+ }
+
+ /* span.video-play {
+ background-color: $time-background;
+ color: #fff;
+ text-align: center;
+ font-size: 34px;
+ line-height: 60px;
+ cursor: pointer;
+ } */
}
#content-docs {
diff --git a/src/scss/partials/_selector.scss b/src/scss/partials/_selector.scss
new file mode 100644
index 00000000..4ce4869f
--- /dev/null
+++ b/src/scss/partials/_selector.scss
@@ -0,0 +1,151 @@
+@keyframes scaleIn {
+ 0% {
+ transform: scale(.2);
+ }
+
+ to {
+ transform: scale(1);
+ }
+}
+
+.selector {
+ height: 100%;
+ display: flex;
+ flex-direction: column;
+
+ &-search-container {
+ flex: 1 1 auto;
+ position: relative;
+ max-height: 132px;
+
+ .scrollable {
+ position: relative;
+ }
+ }
+
+ &-search {
+ padding: 0 24px 0 24px;
+ display: flex;
+ flex-flow: wrap;
+
+ input {
+ border: none;
+ padding: 7px 0px 19px 0px;
+ outline: none;
+ flex: 1 1 auto;
+ }
+ }
+
+ &-user {
+ color: #000;
+ background-color: rgba(112, 117, 121, 0.08);
+ font-size: 16px;
+ padding: 0 17px 0px 0px;
+ line-height: 31px;
+ margin-left: -4px;
+ margin-right: 12px;
+ height: 32px;
+ margin-bottom: 7px;
+ border-radius: 24px;
+ user-select: none;
+ flex: 0 0 auto;
+ transition: .2s all;
+
+ &:hover {
+ background-color: #fae2e3;
+ cursor: pointer;
+
+ .user-avatar:after {
+ opacity: 1;
+ }
+ }
+
+ &.scale-in {
+ animation: scaleIn .15s ease forwards;
+ }
+
+ &.scale-out {
+ animation: scaleIn .1s ease forwards;
+ animation-direction: reverse;
+ }
+
+ .user-avatar {
+ height: 32px !important;
+ width: 32px !important;
+ float: left;
+ margin-right: 8px;
+ overflow: hidden;
+ font-size: 14px;
+
+ &:after {
+ position: absolute;
+ content: $tgico-close;
+ left: 0;
+ top: 0;
+ background-color: #df3f40;
+ height: 100%;
+ width: 100%;
+ z-index: 2;
+ font-size: 23px;
+ line-height: 32px;
+ opacity: 0;
+ transition: .2s opacity;
+ transform: scaleX(-1);
+ }
+ }
+ }
+
+ .chats-container {
+ height: 100%;
+ flex: 1 1 auto;
+ }
+
+ ul {
+ .user-avatar {
+ height: 48px;
+ width: 48px;
+ }
+
+ .user-caption {
+ padding: 1px 3.5px 1px 12px;
+ }
+
+ p {
+ height: 24px;
+ }
+
+ span.user-title {
+ font-weight: normal;
+ }
+
+ span.user-last-message {
+ font-size: 14px;
+ }
+
+ li {
+ padding-bottom: 0;
+
+ > .rp {
+ margin: 0px 9px 0px 8px;
+ padding: 12px 8.5px;
+ }
+ }
+ }
+
+ hr {
+ width: 100%;
+ height: 1px;
+ border: none;
+ background-color: #DADCE0;
+ margin: 0 0 8px;
+ }
+
+ [type="checkbox"] + span {
+ padding-left: calc(9px + 2.25rem);
+ }
+
+ .checkbox {
+ margin-top: 11px;
+ padding-left: 11px;
+ }
+}
\ No newline at end of file
diff --git a/src/scss/partials/_slider.scss b/src/scss/partials/_slider.scss
new file mode 100644
index 00000000..b2ee1fb5
--- /dev/null
+++ b/src/scss/partials/_slider.scss
@@ -0,0 +1,79 @@
+.menu-horizontal {
+ color: $darkgrey;
+ border-bottom: 1px solid $lightgrey;
+ position: relative;
+
+ ul {
+ width: 100%;
+ height: 100%;
+ margin: 0;
+ display: flex;
+ justify-content: space-around;
+ align-items: center;
+ position: relative;
+ z-index: 2;
+ }
+
+ li {
+ display: inline-block;
+ padding: .75rem 1rem;
+ cursor: pointer;
+ text-align: center;
+ flex: 1;
+ user-select: none;
+ font-size: 1rem;
+ font-weight: 500;
+
+ &.active {
+ color: $blue;
+ }
+ }
+
+ &__stripe {
+ position: absolute;
+ background: $blue;
+ //left: 0;
+ left: -2px;
+ transition: .3s transform, .3s width;
+ //transition: .3s transform;
+ bottom: -1px;
+ height: 4px;
+ width: 1px; // need if using transform
+ transform: scaleX(1) translateX(0px);
+ border-top-left-radius: 2px;
+ border-top-right-radius: 2px;
+ z-index: 1;
+ }
+}
+
+.tabs-container {
+ min-width: 100%;
+ width: 100%;
+ display: flex;
+ /* overflow: hidden; */
+ overflow-x: hidden;
+
+ &.animated {
+ transition: .3s transform;
+ }
+
+ > div {
+ width: 100%;
+ max-width: 100%;
+ overflow: hidden;
+ /* transition: .2s all; */
+ display: none;
+
+ &.active {
+ display: flex;
+ flex-direction: column;
+ }
+
+ > div:not(.scroll-padding) {
+ width: 100%;
+ max-width: 100%;
+ /* overflow: hidden; */
+ position: relative;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/scss/style.scss b/src/scss/style.scss
index f2869bd6..27a350c2 100644
--- a/src/scss/style.scss
+++ b/src/scss/style.scss
@@ -42,6 +42,8 @@ $large-screen: 1680px;
@import "partials/ckin";
@import "partials/emojiDropdown";
@import "partials/scrollable";
+@import "partials/slider";
+@import "partials/selector";
@import "partials/popups/popup";
@import "partials/popups/editAvatar";
@@ -169,6 +171,19 @@ input {
}
}
+.btn-corner {
+ position: absolute !important;
+ bottom: 20px;
+ right: 20px;
+ transition: .2s ease;
+ transform: translateY(calc(100% + 20px));
+ z-index: 3;
+
+ &.is-visible {
+ transform: translateY(0px);
+ }
+}
+
.danger {
color: $color-error!important;
}
@@ -263,6 +278,16 @@ input {
}
}
+@keyframes fadeIn {
+ 0% {
+ opacity: 0;
+ }
+
+ to {
+ opacity: 1;
+ }
+}
+
.user-avatar {
color: #fff;
width: 54px;
@@ -286,6 +311,10 @@ input {
height: 100%;
border-radius: inherit;
user-select: none;
+
+ &.fade-in {
+ animation: fadeIn .2s ease forwards;
+ }
}
&[class*=" tgico-"] {
@@ -624,6 +653,23 @@ input {
}
}
+/* .page-signQR {
+ .auth-image {
+ position: relative;
+
+ .sign-logo {
+ width: 36px;
+ height: 36px;
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ background-color: #fff;
+ border-radius: 50%;
+ }
+ }
+} */
+
.page-signUp {
.auth-image {
border-radius: 50%;
@@ -837,6 +883,7 @@ input {
&:not(:checked) + span:after {
background-color: transparent;
+ border-color: #8d969c;
}
&:checked + span:after {
@@ -997,19 +1044,19 @@ input:focus, button:focus {
}
}
-/* // scaling... any units
-$width: 100px;
-
-.loader {
- position: relative;
- margin: 0 auto;
- width: $width;
- &:before {
- content: '';
- display: block;
- padding-top: 100%;
+.btn-primary.btn-circle {
+ .preloader-circular {
+ height: calc(100% - 20px);
+ right: auto;
+ left: auto;
+ margin: 0;
+ top: 10px;
+
+ .preloader-path {
+ stroke: #fff;
+ }
}
-} */
+}
.preloader {
&-circular {
@@ -1164,6 +1211,7 @@ img.emoji {
border-radius: 50%;
height: 54px;
width: 54px;
+ line-height: 54px;
path {
fill: white;
@@ -1271,91 +1319,17 @@ img.emoji {
user-select: text;
}
-.menu-horizontal {
- color: $darkgrey;
- border-bottom: 1px solid $lightgrey;
- position: relative;
-
- ul {
- width: 100%;
- height: 100%;
- margin: 0;
- display: flex;
- justify-content: space-around;
- align-items: center;
- position: relative;
- z-index: 2;
- }
-
- li {
- display: inline-block;
- padding: .75rem 1rem;
- cursor: pointer;
- text-align: center;
- flex: 1;
- user-select: none;
- font-size: 1rem;
- font-weight: 500;
-
- &.active {
- color: $blue;
- }
- }
-
- &__stripe {
- position: absolute;
- background: $blue;
- //left: 0;
- left: -2px;
- transition: .3s transform, .3s width;
- //transition: .3s transform;
- bottom: -1px;
- height: 4px;
- width: 1px; // need if using transform
- transform: scaleX(1) translateX(0px);
- border-top-left-radius: 2px;
- border-top-right-radius: 2px;
- z-index: 1;
- }
-}
-
-.tabs-container {
- min-width: 100%;
- width: 100%;
- display: flex;
- /* overflow: hidden; */
- overflow-x: hidden;
-
- &.animated {
- transition: .3s transform;
- }
-
- > div {
- width: 100%;
- max-width: 100%;
- overflow: hidden;
- /* transition: .2s all; */
- display: none;
-
- &.active {
- display: flex;
- flex-direction: column;
- }
-
- > div:not(.scroll-padding) {
- width: 100%;
- padding: 7.5px;
- max-width: 100%;
- overflow: hidden;
- position: relative;
- }
- }
-}
-
.justify-start {
justify-content: flex-start!important;
}
+.position-center {
+ position: absolute;
+ left: 50%;
+ top: 50%;
+ transform: translate(-50%, -50%);
+}
+
.page-chats {
/* display: grid; */
/* grid-template-columns: 25% 50%; */
@@ -1396,8 +1370,7 @@ img.emoji {
}
}
- #search-container, #chats-archived-container, .sidebar-search {
- display: none;
+ #search-container, .sidebar-search {
flex-direction: column;
width: 100%;
max-height: 100%;
@@ -1408,10 +1381,6 @@ img.emoji {
top: 0;
z-index: 3;
background: #fff;
-
- &.active {
- display: flex;
- }
}
@media (min-width: $large-screen) {