Browse Source

Contacs with filter & forward (no search and load more) & QR log in & minor improvements

master
morethanwords 4 years ago
parent
commit
1c63bf30c1
  1. 15
      package-lock.json
  2. 1
      package.json
  3. 230
      src/components/appForward.ts
  4. 19
      src/components/appSearch.ts
  5. 1
      src/components/bubbleGroups.ts
  6. 6
      src/components/chatInput.ts
  7. 29
      src/components/emoticonsDropdown.ts
  8. 10
      src/components/lazyLoadQueue.ts
  9. 9
      src/components/misc.ts
  10. 7
      src/components/preloader.ts
  11. 15
      src/components/scrollable_new.ts
  12. 46
      src/components/wrappers.ts
  13. 5
      src/lib/appManagers/apiUpdatesManager.ts
  14. 11
      src/lib/appManagers/appChatsManager.ts
  15. 82
      src/lib/appManagers/appDialogsManager.ts
  16. 78
      src/lib/appManagers/appImManager.ts
  17. 523
      src/lib/appManagers/appMessagesManager.ts
  18. 6
      src/lib/appManagers/appPeersManager.ts
  19. 36
      src/lib/appManagers/appPhotosManager.ts
  20. 171
      src/lib/appManagers/appSidebarLeft.ts
  21. 208
      src/lib/appManagers/appSidebarRight.ts
  22. 172
      src/lib/appManagers/appUsersManager.ts
  23. 2
      src/lib/bin_utils.ts
  24. 2
      src/lib/lottieLoader.ts
  25. 12
      src/lib/mtproto/apiManager.ts
  26. 25
      src/lib/mtproto/mtproto.worker.js
  27. 4
      src/lib/mtproto/mtprotoworker.ts
  28. 2
      src/lib/mtproto/serverTimeManager.ts
  29. 158
      src/pages/pageSignQR.ts
  30. 1
      src/pages/pageSignUp.ts
  31. 77
      src/scss/components/_global.scss
  32. 25
      src/scss/components/_typography.scss
  33. 86
      src/scss/partials/_chat.scss
  34. 73
      src/scss/partials/_chatlist.scss
  35. 140
      src/scss/partials/_emojiDropdown.scss
  36. 1
      src/scss/partials/_ico.scss
  37. 71
      src/scss/partials/_leftSidebar.scss
  38. 8
      src/scss/partials/_mediaViewer.scss
  39. 37
      src/scss/partials/_rightSIdebar.scss
  40. 151
      src/scss/partials/_selector.scss
  41. 79
      src/scss/partials/_slider.scss
  42. 167
      src/scss/style.scss

15
package-lock.json generated

@ -11779,6 +11779,21 @@ @@ -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",

1
package.json

@ -46,6 +46,7 @@ @@ -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",

230
src/components/appForward.ts

@ -0,0 +1,230 @@ @@ -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', '<div class="checkbox"><label><input type="checkbox"><span></span></label></div>');
let subtitle = '';
if(peerID < 0) {
subtitle = appChatsManager.getChatMembersString(-peerID);
} else if(peerID == myID) {
subtitle = 'chat with yourself';
} else {
subtitle = appUsersManager.getUserStatusString(peerID);
if(subtitle == 'online') {
subtitle = `<i>${subtitle}</i>`;
}
}
dom.lastMessageSpan.innerHTML = subtitle;
});
});
this.chatList.addEventListener('click', (e) => {
let target = e.target as HTMLElement;
cancelEvent(e);
if(this.freezed) return;
if(target.tagName != 'LI') {
target = findUpTag(target, 'LI');
}
if(!target) return;
let peerID = +target.getAttribute('data-peerID');
target.classList.toggle('active');
if(peerID in this.selected) {
this.remove(peerID);
} else {
this.add(peerID);
}
let checkbox = target.querySelector('input') as HTMLInputElement;
checkbox.checked = !checkbox.checked;
});
this.selectedContainer.addEventListener('click', (e) => {
if(this.freezed) return;
let target = e.target as HTMLElement;
target = findUpClassName(target, 'selector-user');
if(!target) return;
let peerID = target.dataset.peerID;
let li = this.chatList.querySelector('[data-peerid="' + peerID + '"]') as HTMLElement;
li.click();
});
this.container.append(topContainer, delimiter, this.chatsContainer);
appendTo.append(this.container);
}
private add(peerID: number) {
let div = document.createElement('div');
div.classList.add('selector-user', 'scale-in');
div.dataset.peerID = '' + peerID;
this.selected[peerID] = div;
let title = appPeersManager.getPeerTitle(peerID, false, true);
let avatarDiv = document.createElement('div');
avatarDiv.classList.add('user-avatar', 'tgico');
appProfileManager.putPhoto(avatarDiv, peerID);
div.innerHTML = title;
div.insertAdjacentElement('afterbegin', avatarDiv);
this.selectedContainer.insertBefore(div, this.searchInput);
this.selectedScrollable.scrollTop = this.selectedScrollable.scrollHeight;
this.onChange(Object.keys(this.selected).length);
}
private remove(peerID: number) {
let div = this.selected[peerID];
div.classList.remove('scale-in');
void div.offsetWidth;
div.classList.add('scale-out');
div.addEventListener('animationend', () => {
delete this.selected[peerID];
div.remove();
this.onChange(Object.keys(this.selected).length);
}, {once: true});
}
public getSelected() {
return Object.keys(this.selected).map(p => +p);
}
}
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();

19
src/components/appSearch.ts

@ -4,17 +4,19 @@ import appMessagesIDsManager from "../lib/appManagers/appMessagesIDsManager"; @@ -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 { @@ -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 { @@ -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 { @@ -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, '<i>$1</i>');
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) {

1
src/components/bubbleGroups.ts

@ -154,6 +154,7 @@ export default class BubbleGroups { @@ -154,6 +154,7 @@ export default class BubbleGroups {
cleanup() {
this.bubblesByGroups = [];
this.groups = [];
/* for(let value of this.updateRAFs.values()) {
window.cancelAnimationFrame(value);
}

6
src/components/chatInput.ts

@ -393,7 +393,7 @@ export class ChatInput { @@ -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 { @@ -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) : '';

29
src/components/emoticonsDropdown.ts

@ -141,6 +141,15 @@ const initEmoticonsDropdown = (pageEl: HTMLDivElement, @@ -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, @@ -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, @@ -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, @@ -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, @@ -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);
}
})
]);

10
src/components/lazyLoadQueue.ts

@ -15,6 +15,7 @@ export default class LazyLoadQueue { @@ -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 { @@ -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 { @@ -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();

9
src/components/misc.ts

@ -107,10 +107,13 @@ let set = (elem: HTMLElement | HTMLImageElement | SVGImageElement | HTMLSourceEl @@ -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 @@ -119,6 +122,8 @@ export function renderImageFromUrl(elem: HTMLElement | HTMLImageElement | SVGIma
loadedURLs[url] = true;
};
}
return false;
}
export function putPreloader(elem: Element, returnDiv = false) {

7
src/components/preloader.ts

@ -49,11 +49,14 @@ export default class ProgressivePreloader { @@ -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;

15
src/components/scrollable_new.ts

@ -64,7 +64,7 @@ export default class Scrollable { @@ -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 { @@ -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 { @@ -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 { @@ -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;
});
}

46
src/components/wrappers.ts

@ -115,6 +115,23 @@ export function wrapVideo({doc, container, message, boxWidth, boxHeight, withTai @@ -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 @@ -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 @@ -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 @@ -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 @@ -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?: ( @@ -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?: ( @@ -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?: ( @@ -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) { @@ -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 {

5
src/lib/appManagers/apiUpdatesManager.ts

@ -24,8 +24,8 @@ export class ApiUpdatesManager { @@ -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 { @@ -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;

11
src/lib/appManagers/appChatsManager.ts

@ -1,4 +1,4 @@ @@ -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 { @@ -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 { @@ -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);

82
src/lib/appManagers/appDialogsManager.ts

@ -1,8 +1,8 @@ @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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);

78
src/lib/appManagers/appImManager.ts

@ -26,6 +26,7 @@ import Scrollable from '../../components/scrollable_new'; @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -2306,4 +2295,5 @@ export class AppImManager {
}
const appImManager = new AppImManager();
(window as any).appImManager = appImManager;
export default appImManager;

523
src/lib/appManagers/appMessagesManager.ts

@ -38,7 +38,7 @@ export type HistoryResult = { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -1210,20 +1184,14 @@ export class AppMessagesManager {
});
}
public getTopMessages(limit: number, folderID = -1): Promise<number> {
var dialogs = this.dialogsStorage.dialogs;
public getTopMessages(limit: number, folderID: number): Promise<number> {
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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -2826,7 +2542,7 @@ export class AppMessagesManager {
}
})
break;
}
} */
case 'updateEditMessage':
case 'updateEditChannelMessage': {
@ -3092,37 +2808,41 @@ export class AppMessagesManager { @@ -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 { @@ -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;

6
src/lib/appManagers/appPeersManager.ts

@ -37,7 +37,7 @@ const AppPeersManager = { @@ -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 = { @@ -53,6 +53,10 @@ const AppPeersManager = {
} else {
title = peer.title;
}
if(onlyFirstName) {
title = title.split(' ')[0];
}
return plainText ? title : RichTextProcessor.wrapEmojiText(title);
},

36
src/lib/appManagers/appPhotosManager.ts

@ -27,6 +27,12 @@ export class AppPhotosManager { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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];

171
src/lib/appManagers/appSidebarLeft.ts

@ -5,6 +5,16 @@ import appImManager from "./appImManager"; @@ -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 { @@ -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<void>;
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 { @@ -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 { @@ -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 { @@ -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 { @@ -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' ? `<i>${status}</i>` : status;
});
if(!sorted.length) renderPage = undefined;
};
renderPage();
this.contactsScrollable.onScrolledBottom = () => {
if(renderPage) {
renderPage();
} else {
this.contactsScrollable.onScrolledBottom = null;
}
};
});
}
public selectTab(id: number) {
this.historyTabIDs.push(id);
this._selectTab(id);
}
}

208
src/lib/appManagers/appSidebarRight.ts

@ -59,12 +59,6 @@ class AppSidebarRight { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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;
});

172
src/lib/appManagers/appUsersManager.ts

@ -5,14 +5,41 @@ import appChatsManager from "./appChatsManager"; @@ -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<number[]>;
public contactsList: number[];
public myID: number;
constructor() {
@ -80,28 +107,27 @@ export class AppUsersManager { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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)
} : {

2
src/lib/bin_utils.ts

@ -66,7 +66,7 @@ export function bytesFromHex(hexString: string) { @@ -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 = ''

2
src/lib/lottieLoader.ts

@ -20,7 +20,7 @@ class LottieLoader { @@ -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;

12
src/lib/mtproto/apiManager.ts

@ -59,6 +59,10 @@ export class ApiManager { @@ -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 { @@ -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 { @@ -95,9 +97,9 @@ export class ApiManager {
error.handled = true;
this.telegramMeNotify(false);
this.mtpClearStorage();
}).then(() => {
})/* .then(() => {
location.pathname = '/';
});
}) */;
}
public mtpClearStorage() {

25
src/lib/mtproto/mtproto.worker.js

@ -26,15 +26,26 @@ ctx.onmessage = function(e) { @@ -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);
}
}
}

4
src/lib/mtproto/mtprotoworker.ts

@ -125,6 +125,10 @@ class ApiManagerProxy extends CryptoWorkerMethods { @@ -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);

2
src/lib/mtproto/serverTimeManager.ts

@ -8,7 +8,7 @@ export class ServerTimeManager { @@ -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

158
src/pages/pageSignQR.ts

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

1
src/pages/pageSignUp.ts

@ -5,7 +5,6 @@ import pageIm from './pageIm'; @@ -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,

77
src/scss/components/_global.scss

@ -32,13 +32,6 @@ a { @@ -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 { @@ -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 @@ -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;
}

25
src/scss/components/_typography.scss

@ -1,39 +1,16 @@ @@ -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; }

86
src/scss/partials/_chat.scss

@ -1,4 +1,5 @@ @@ -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; @@ -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; @@ -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; @@ -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; @@ -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; @@ -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; @@ -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; @@ -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; @@ -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; @@ -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; @@ -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; @@ -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; @@ -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; @@ -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 {

73
src/scss/partials/_chatlist.scss

@ -262,46 +262,57 @@ @@ -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;
}
}

140
src/scss/partials/_emojiDropdown.scss

@ -49,42 +49,57 @@ @@ -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 @@ @@ -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%;
}
}
}
}

1
src/scss/partials/_ico.scss

@ -4,3 +4,4 @@ $tgico-font-path: "../../assets/fonts" !default; @@ -4,3 +4,4 @@ $tgico-font-path: "../../assets/fonts" !default;
$tgico-check: "\e900";
$tgico-checks: "\e95a";
$tgico-sending: "\e919";
$tgico-close: "\e943";

71
src/scss/partials/_leftSidebar.scss

@ -8,6 +8,10 @@ @@ -8,6 +8,10 @@
position: relative;
}
.sidebar-slider {
height: 100%;
}
.sidebar-header__btn-container {
position: relative;
width: 39.75px;
@ -67,5 +71,70 @@ @@ -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;
}
}

8
src/scss/partials/_mediaViewer.scss

@ -122,12 +122,12 @@ @@ -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;

37
src/scss/partials/_rightSIdebar.scss

@ -36,6 +36,14 @@ @@ -36,6 +36,14 @@
flex: 1 1 auto;
}
}
.sidebar-search {
display: none;
&.active {
display: flex;
}
}
}
.profile {
@ -201,7 +209,7 @@ @@ -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 @@ @@ -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 @@ @@ -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 {

151
src/scss/partials/_selector.scss

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

79
src/scss/partials/_slider.scss

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

167
src/scss/style.scss

@ -42,6 +42,8 @@ $large-screen: 1680px; @@ -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 { @@ -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 { @@ -263,6 +278,16 @@ input {
}
}
@keyframes fadeIn {
0% {
opacity: 0;
}
to {
opacity: 1;
}
}
.user-avatar {
color: #fff;
width: 54px;
@ -286,6 +311,10 @@ input { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -1408,10 +1381,6 @@ img.emoji {
top: 0;
z-index: 3;
background: #fff;
&.active {
display: flex;
}
}
@media (min-width: $large-screen) {

Loading…
Cancel
Save