Browse Source

Folders almost ready

New CSS
Polls
master
morethanwords 4 years ago
parent
commit
47ce25fce2
  1. 120
      src/components/appSelectPeers.ts
  2. 4
      src/components/emoticonsDropdown.ts
  3. 18
      src/components/misc.ts
  4. 44
      src/components/poll.ts
  5. 5
      src/components/popupCreatePoll.ts
  6. 14
      src/components/scrollable_new.ts
  7. 187
      src/components/sidebarLeft/chatFolders.ts
  8. 6
      src/components/sidebarLeft/contacts.ts
  9. 249
      src/components/sidebarLeft/editFolder.ts
  10. 186
      src/components/sidebarLeft/includedChats.ts
  11. 7
      src/components/sidebarLeft/settings.ts
  12. 26
      src/components/slider.ts
  13. 92
      src/index.hbs
  14. 689
      src/lib/appManagers/appDialogsManager.ts
  15. 916
      src/lib/appManagers/appMessagesManager.ts
  16. 7
      src/lib/appManagers/appPeersManager.ts
  17. 28
      src/lib/appManagers/appSidebarLeft.ts
  18. 2
      src/lib/appManagers/appSidebarRight.ts
  19. 41
      src/lib/appManagers/appUsersManager.ts
  20. 15
      src/lib/lottieLoader.ts
  21. 2
      src/lib/mtproto/schema.ts
  22. 11
      src/lib/utils.js
  23. 22
      src/pages/pageAuthCode.ts
  24. 11
      src/pages/pagePassword.ts
  25. 72
      src/scss/partials/_chat.scss
  26. 86
      src/scss/partials/_chatBubble.scss
  27. 13
      src/scss/partials/_chatlist.scss
  28. 71
      src/scss/partials/_emojiDropdown.scss
  29. 227
      src/scss/partials/_leftSidebar.scss
  30. 6
      src/scss/partials/_rightSidebar.scss
  31. 4
      src/scss/partials/_selector.scss
  32. 6
      src/scss/partials/_sidebar.scss
  33. 2
      src/scss/partials/_slider.scss
  34. 16
      src/scss/partials/popups/_createPoll.scss
  35. 9
      src/scss/partials/popups/_mediaAttacher.scss
  36. 27
      src/scss/style.scss
  37. 2
      webpack.common.js

120
src/components/appSelectPeers.ts

@ -1,6 +1,6 @@ @@ -1,6 +1,6 @@
import Scrollable from "./scrollable_new";
import appMessagesManager, { Dialog } from "../lib/appManagers/appMessagesManager";
import { $rootScope, cancelEvent, findUpTag, findUpClassName } from "../lib/utils";
import { $rootScope, cancelEvent, findUpClassName, findUpTag, findUpAttribute } from "../lib/utils";
import appDialogsManager from "../lib/appManagers/appDialogsManager";
import appChatsManager from "../lib/appManagers/appChatsManager";
import appUsersManager from "../lib/appManagers/appUsersManager";
@ -9,15 +9,16 @@ import appPhotosManager from "../lib/appManagers/appPhotosManager"; @@ -9,15 +9,16 @@ import appPhotosManager from "../lib/appManagers/appPhotosManager";
export class AppSelectPeers {
public container = document.createElement('div');
private list = document.createElement('ul');
private chatsContainer = document.createElement('div');
private scrollable: Scrollable;
private selectedScrollable: Scrollable;
public list = document.createElement('ul');
public chatsContainer = document.createElement('div');
public scrollable: Scrollable;
public selectedScrollable: Scrollable;
private selectedContainer = document.createElement('div');
private input = document.createElement('input');
public selectedContainer = document.createElement('div');
public input = document.createElement('input');
private selected: {[peerID: number]: HTMLDivElement} = {};
//public selected: {[peerID: number]: HTMLElement} = {};
public selected = new Set<any>();
public freezed = false;
@ -29,9 +30,13 @@ export class AppSelectPeers { @@ -29,9 +30,13 @@ export class AppSelectPeers {
private query = '';
private cachedContacts: number[];
constructor(private appendTo: HTMLDivElement, private onChange?: (length: number) => void, private peerType: 'contacts' | 'dialogs' = 'dialogs', onFirstRender?: () => void) {
constructor(private appendTo: HTMLElement, private onChange?: (length: number) => void, private peerType: 'contacts' | 'dialogs' = 'dialogs', onFirstRender?: () => void, private renderResultsFunc?: (peerIDs: number[]) => void) {
this.container.classList.add('selector');
if(!this.renderResultsFunc) {
this.renderResultsFunc = this.renderResults;
}
let topContainer = document.createElement('div');
topContainer.classList.add('selector-search-container');
@ -49,27 +54,23 @@ export class AppSelectPeers { @@ -49,27 +54,23 @@ export class AppSelectPeers {
this.scrollable = new Scrollable(this.chatsContainer);
this.scrollable.setVirtualContainer(this.list);
this.list.addEventListener('click', (e) => {
let target = e.target as HTMLElement;
this.chatsContainer.addEventListener('click', (e) => {
const target = findUpAttribute(e.target, 'data-peerID') as HTMLElement;
cancelEvent(e);
if(this.freezed) return;
if(target.tagName != 'LI') {
target = findUpTag(target, 'LI');
}
if(!target) return;
if(this.freezed) return;
let peerID = +target.getAttribute('data-peerID');
let key: any = target.getAttribute('data-peerID');
key = +key || key;
target.classList.toggle('active');
if(peerID in this.selected) {
this.remove(peerID);
if(this.selected.has(key)) {
this.remove(key);
} else {
this.add(peerID);
this.add(key);
}
let checkbox = target.querySelector('input') as HTMLInputElement;
const checkbox = target.querySelector('input') as HTMLInputElement;
checkbox.checked = !checkbox.checked;
});
@ -80,9 +81,13 @@ export class AppSelectPeers { @@ -80,9 +81,13 @@ export class AppSelectPeers {
if(!target) return;
let peerID = target.dataset.peerID;
let li = this.list.querySelector('[data-peerid="' + peerID + '"]') as HTMLElement;
li.click();
const peerID = target.dataset.key;
const li = this.chatsContainer.querySelector('[data-peerid="' + peerID + '"]') as HTMLElement;
if(!li) {
this.remove(+peerID || peerID);
} else {
li.click();
}
});
this.input.addEventListener('input', () => {
@ -110,12 +115,15 @@ export class AppSelectPeers { @@ -110,12 +115,15 @@ export class AppSelectPeers {
this.container.append(topContainer, delimiter, this.chatsContainer);
appendTo.append(this.container);
let getResultsPromise = this.getMoreResults() as Promise<any>;
if(onFirstRender) {
getResultsPromise.then(() => {
onFirstRender();
});
}
// WARNING TIMEOUT
setTimeout(() => {
let getResultsPromise = this.getMoreResults() as Promise<any>;
if(onFirstRender) {
getResultsPromise.then(() => {
onFirstRender();
});
}
}, 0);
}
private async getMoreDialogs() {
@ -144,7 +152,7 @@ export class AppSelectPeers { @@ -144,7 +152,7 @@ export class AppSelectPeers {
this.offsetIndex = newOffsetIndex;
this.renderResults(dialogs.map(dialog => dialog.peerID));
this.renderResultsFunc(dialogs.map(dialog => dialog.peerID));
this.promise = null;
}
@ -162,7 +170,7 @@ export class AppSelectPeers { @@ -162,7 +170,7 @@ export class AppSelectPeers {
if(this.cachedContacts.length) {
const pageCount = appPhotosManager.windowH / 72 * 1.25 | 0;
const arr = this.cachedContacts.splice(0, pageCount);
this.renderResults(arr);
this.renderResultsFunc(arr);
}
}
@ -178,7 +186,10 @@ export class AppSelectPeers { @@ -178,7 +186,10 @@ export class AppSelectPeers {
//console.log('will renderResults:', peerIDs);
peerIDs.forEach(peerID => {
const {dom} = appDialogsManager.addDialog(peerID, this.scrollable, false, false);
dom.containerEl.insertAdjacentHTML('afterbegin', '<div class="checkbox"><label><input type="checkbox"><span></span></label></div>');
const selected = this.selected.has(peerID);
dom.containerEl.insertAdjacentHTML('afterbegin', `<div class="checkbox"><label><input type="checkbox" ${selected ? 'checked' : ''}><span></span></label></div>`);
if(selected) dom.listEl.classList.add('active');
let subtitle = '';
if(peerID < 0) {
@ -196,40 +207,53 @@ export class AppSelectPeers { @@ -196,40 +207,53 @@ export class AppSelectPeers {
});
}
private add(peerID: number) {
public add(peerID: any, title?: string) {
console.trace('add');
const div = document.createElement('div');
div.classList.add('selector-user', 'scale-in');
div.dataset.peerID = '' + peerID;
this.selected[peerID] = div;
const title = appPeersManager.getPeerTitle(peerID, false, true);
const avatarEl = document.createElement('avatar-element');
avatarEl.classList.add('selector-user-avatar', 'tgico');
avatarEl.setAttribute('dialog', '1');
avatarEl.setAttribute('peer', '' + peerID);
div.innerHTML = title;
div.dataset.key = '' + peerID;
this.selected.add(peerID);
if(typeof(peerID) === 'number') {
if(title === undefined) {
title = peerID == $rootScope.myID ? 'Saved' : appPeersManager.getPeerTitle(peerID, false, true);
}
avatarEl.setAttribute('peer', '' + peerID);
}
if(title) {
div.innerHTML = title;
}
div.insertAdjacentElement('afterbegin', avatarEl);
this.selectedContainer.insertBefore(div, this.input);
this.selectedScrollable.scrollTop = this.selectedScrollable.scrollHeight;
this.onChange && this.onChange(Object.keys(this.selected).length);
//this.selectedScrollable.scrollTop = this.selectedScrollable.scrollHeight;
this.selectedScrollable.scrollTo(this.selectedScrollable.scrollHeight, true, true);
this.onChange && this.onChange(this.selected.size);
return div;
}
private remove(peerID: number) {
const div = this.selected[peerID];
public remove(key: any) {
//const div = this.selected[peerID];
const div = this.selectedContainer.querySelector(`[data-key="${key}"]`) as HTMLElement;
div.classList.remove('scale-in');
void div.offsetWidth;
div.classList.add('scale-out');
div.addEventListener('animationend', () => {
delete this.selected[peerID];
this.selected.delete(key);
div.remove();
this.onChange && this.onChange(Object.keys(this.selected).length);
this.onChange && this.onChange(this.selected.size);
}, {once: true});
}
public getSelected() {
return Object.keys(this.selected).map(p => +p);
return [...this.selected];
}
}

4
src/components/emoticonsDropdown.ts

@ -320,8 +320,8 @@ class StickersTab implements EmoticonsTab { @@ -320,8 +320,8 @@ class StickersTab implements EmoticonsTab {
loop: true,
autoplay: false,
animationData: JSON.parse(json),
width: 40,
height: 40
width: 32,
height: 32
}, EMOTICONSSTICKERGROUP);
});

18
src/components/misc.ts

@ -5,6 +5,7 @@ let rippleClickID = 0; @@ -5,6 +5,7 @@ let rippleClickID = 0;
export function ripple(elem: HTMLElement, callback: (id: number) => Promise<boolean | void> = () => Promise.resolve(), onEnd: (id: number) => void = null) {
//return;
if(elem.querySelector('.c-ripple')) return;
elem.classList.add('rp');
let r = document.createElement('div');
r.classList.add('c-ripple');
@ -250,13 +251,28 @@ export function horizontalMenu(tabs: HTMLElement, content: HTMLElement, onClick? @@ -250,13 +251,28 @@ export function horizontalMenu(tabs: HTMLElement, content: HTMLElement, onClick?
let prevTabContent: HTMLElement = null;
let prevId = -1;
const selectTab = async(id: number) => {
const selectTab = (id: number) => {
if(id == prevId) return false;
//console.log('selectTab id:', id);
const p = prevTabContent;
const tabContent = content.children[id] as HTMLElement;
if(content.dataset.slider == 'none') {
if(p) {
p.classList.remove('active');
}
tabContent.classList.add('active');
prevId = id;
prevTabContent = tabContent;
if(onTransitionEnd) onTransitionEnd();
return;
}
const toRight = prevId < id;
if(prevId != -1) {
if(tabs || content.dataset.slider == 'tabs') {

44
src/components/poll.ts

@ -241,6 +241,10 @@ export default class PollElement extends HTMLElement { @@ -241,6 +241,10 @@ export default class PollElement extends HTMLElement {
this.classList.add('is-quiz');
if(poll.close_period && poll.close_date) {
const timeLeftDiv = document.createElement('div');
timeLeftDiv.classList.add('poll-time');
this.descDiv.append(timeLeftDiv);
const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
//svg.setAttributeNS(null, 'viewBox', '0 0 15 15');
svg.classList.add('poll-quiz-timer');
@ -248,30 +252,52 @@ export default class PollElement extends HTMLElement { @@ -248,30 +252,52 @@ export default class PollElement extends HTMLElement {
this.quizTimer = svg;
const strokeWidth = 2;
const radius = (15 / 2) - (strokeWidth * 2);
const radius = 7;
const circumference = 2 * Math.PI * radius;
const circle = document.createElementNS("http://www.w3.org/2000/svg", "circle");
circle.classList.add('poll-quiz-timer-circle');
circle.setAttributeNS(null, 'cx', '15');
circle.setAttributeNS(null, 'cy', '15');
circle.setAttributeNS(null, 'cx', '16');
circle.setAttributeNS(null, 'cy', '16');
circle.setAttributeNS(null, 'r', '' + radius);
circle.setAttributeNS(null, 'stroke-width', '' + strokeWidth);
svg.append(circle);
this.descDiv.append(svg);
const period = poll.close_period * 1000;
const closeTime = (poll.close_date - serverTimeManager.serverTimeOffset) * 1000;
// let time = Date.now();
// let percents = (closeTime - time) / period;
// timeLeftDiv.innerHTML = String((closeTime - time) / 1000 + 1 | 0).toHHMMSS();
// // @ts-ignore
// circle.style.strokeDashoffset = circumference + percents * circumference;
// circle.style.strokeDasharray = ${circumference} ${circumference};
this.quizInterval = setInterval(() => {
const time = Date.now();
const totalLength = circle.getTotalLength();
const percents = (closeTime - time) / period;
circle.style.strokeDasharray = '' + (percents * totalLength) + ', ' + circumference;
const timeLeft = (closeTime - time) / 1000 + 1 | 0;
timeLeftDiv.innerHTML = String(timeLeft).toHHMMSS();
if (timeLeft <= 5) {
timeLeftDiv.style.color = '#ee545c';
circle.style.stroke = '#ee545c';
}
//timeLeftDiv.style.visibility = 'visible';
// @ts-ignore
circle.style.strokeDashoffset = circumference + percents * circumference;
circle.style.strokeDasharray = `${circumference} ${circumference}`;
if(time >= closeTime) {
clearInterval(this.quizInterval);
timeLeftDiv.innerHTML = '';
// @ts-ignore
circle.style.strokeDashoffset = circumference;
this.quizInterval = 0;
// нужно запросить апдейт чтобы опрос обновился
@ -487,7 +513,7 @@ export default class PollElement extends HTMLElement { @@ -487,7 +513,7 @@ export default class PollElement extends HTMLElement {
* все приложения накладывают аватарку первую на вторую, а в макете зато вторая на первую, ЛОЛ!
*/
results.recent_voters/* .slice().reverse() */.forEach((userID, idx) => {
const style = idx == 0 ? '' : `style="transform: translateX(-${idx * 5}px);"`;
const style = idx == 0 ? '' : `style="transform: translateX(-${idx * 3}px);"`;
html += `<avatar-element dialog="0" peer="${userID}" ${style}></avatar-element>`;
});
this.avatarsDiv.innerHTML = html;

5
src/components/popupCreatePoll.ts

@ -29,17 +29,20 @@ export default class PopupCreatePoll extends PopupElement { @@ -29,17 +29,20 @@ export default class PopupCreatePoll extends PopupElement {
this.title.innerText = 'New Poll';
const questionField = InputField('Ask a question', 'Ask a question', 'question');
const questionField = InputField('Ask a Question', 'Ask a Question', 'question');
this.questionInput = questionField.firstElementChild as HTMLInputElement;
this.header.append(questionField);
const hr = document.createElement('hr');
const d = document.createElement('div');
d.classList.add('caption');
d.innerText = 'Options';
this.questions = document.createElement('div');
this.questions.classList.add('poll-create-questions');
this.body.parentElement.insertBefore(hr, this.body);
this.body.append(d, this.questions);
this.confirmBtn.addEventListener('click', this.onSubmitClick);

14
src/components/scrollable_new.ts

@ -82,6 +82,8 @@ export default class Scrollable { @@ -82,6 +82,8 @@ export default class Scrollable {
public scrollLocked = 0;
public isVisible = false;
private reorderTimeout: number;
private setVisible(element: HTMLElement) {
if(this.visible.has(element)) return;
@ -373,9 +375,15 @@ export default class Scrollable { @@ -373,9 +375,15 @@ export default class Scrollable {
}
public reorder() {
(Array.from(this.splitUp.children) as HTMLElement[]).forEach((el, idx) => {
el.dataset.virtual = '' + idx;
});
if(!this.splitUp || this.reorderTimeout) return;
this.reorderTimeout = setTimeout(() => {
this.reorderTimeout = 0;
(Array.from(this.splitUp.children) as HTMLElement[]).forEach((el, idx) => {
el.dataset.virtual = '' + idx;
});
}, 0);
}
public updateElement(element: HTMLElement) {

187
src/components/sidebarLeft/chatFolders.ts

@ -0,0 +1,187 @@ @@ -0,0 +1,187 @@
import { SliderTab } from "../slider";
import lottieLoader, { RLottiePlayer } from "../../lib/lottieLoader";
import apiManager from "../../lib/mtproto/mtprotoworker";
import appMessagesManager, { DialogFilter } from "../../lib/appManagers/appMessagesManager";
import { RichTextProcessor } from "../../lib/richtextprocessor";
import appPeersManager from "../../lib/appManagers/appPeersManager";
import { ripple } from "../misc";
import { $rootScope, cancelEvent } from "../../lib/utils";
import appSidebarLeft from "../../lib/appManagers/appSidebarLeft";
type DialogFilterSuggested = {
_: 'dialogFilterSuggested',
filter: DialogFilter,
description: string
};
export default class AppChatFoldersTab implements SliderTab {
public container: HTMLElement;
public createFolderBtn: HTMLElement;
private foldersContainer: HTMLElement;
private suggestedContainer: HTMLElement;
private stickerContainer: HTMLElement;
private animation: RLottiePlayer;
private filtersRendered: {[filterID: number]: HTMLElement} = {};
private renderFolder(dialogFilter: DialogFilterSuggested | DialogFilter, container?: HTMLElement, div: HTMLElement = document.createElement('div')) {
let filter: DialogFilter;
let description = '';
let d: string[] = [];
if(dialogFilter._ == 'dialogFilterSuggested') {
filter = dialogFilter.filter;
description = dialogFilter.description;
} else {
filter = dialogFilter;
description = '';
const filterID = filter.id;
if(!this.filtersRendered.hasOwnProperty(filter.id)) {
div.addEventListener('click', () => {
appSidebarLeft.editFolderTab.open(appMessagesManager.filtersStorage.filters[filterID]);
});
}
this.filtersRendered[filter.id] = div;
let enabledFilters = Object.keys(filter.pFlags).length;
/* (['include_peers', 'exclude_peers'] as ['include_peers', 'exclude_peers']).forEach(key => {
enabledFilters += +!!filter[key].length;
}); */
if(enabledFilters == 1) {
description = 'All ';
const pFlags = filter.pFlags;
if(pFlags.contacts) description += 'Contacts';
else if(pFlags.non_contacts) description += 'Non-Contacts';
else if(pFlags.groups) description += 'Groups';
else if(pFlags.broadcasts) description += 'Channels';
else if(pFlags.bots) description += 'Bots';
else if(pFlags.exclude_muted) description += 'Unmuted';
else if(pFlags.exclude_read) description += 'Unread';
else if(pFlags.exclude_archived) description += 'Unarchived';
d.push(description);
} else {
const folder = appMessagesManager.dialogsStorage.getFolder(filter.id);
let chats = 0, channels = 0, groups = 0;
for(const dialog of folder) {
if(appPeersManager.isAnyGroup(dialog.peerID)) groups++;
else if(appPeersManager.isBroadcast(dialog.peerID)) channels++;
else chats++;
}
if(chats) d.push(chats + ' chats');
if(channels) d.push(channels + ' channels');
if(groups) d.push(groups + ' groups');
}
}
div.classList.add('category');
div.innerHTML = `
<div>
<p>${RichTextProcessor.wrapEmojiText(filter.title)}</p>
<p>${d.length ? d.join(', ') : description}</p>
</div>
`;
ripple(div);
if(container) container.append(div);
return div;
}
init() {
this.container = document.querySelector('.chat-folders-container');
this.stickerContainer = this.container.querySelector('.sticker-container');
this.foldersContainer = this.container.querySelector('.folders-my');
this.suggestedContainer = this.container.querySelector('.folders-suggested');
this.createFolderBtn = this.container.querySelector('.btn-create-folder');
this.createFolderBtn.addEventListener('click', () => {
appSidebarLeft.editFolderTab.open();
});
lottieLoader.loadAnimationFromURL({
container: this.stickerContainer,
loop: false,
autoplay: true,
width: 86,
height: 86
}, 'assets/img/Folders_1.tgs').then(player => {
this.animation = player;
});
appMessagesManager.filtersStorage.getDialogFilters().then(filters => {
for(const filterID in filters) {
const filter = filters[filterID];
this.renderFolder(filter, this.foldersContainer);
}
});
$rootScope.$on('filter_update', (e: CustomEvent) => {
const filter: DialogFilter = e.detail;
if(this.filtersRendered.hasOwnProperty(filter.id)) {
this.renderFolder(filter, null, this.filtersRendered[filter.id]);
} else {
this.renderFolder(filter, this.foldersContainer);
}
this.getSuggestedFilters();
});
$rootScope.$on('filter_delete', (e: CustomEvent) => {
const filter: DialogFilter = e.detail;
if(this.filtersRendered.hasOwnProperty(filter.id)) {
/* for(const suggested of this.suggestedFilters) {
if(deepEqual(suggested.filter, filter)) {
}
} */
this.getSuggestedFilters();
this.filtersRendered[filter.id].remove();
delete this.filtersRendered[filter.id]
}
});
}
private getSuggestedFilters() {
apiManager.invokeApi('messages.getSuggestedDialogFilters').then(suggestedFilters => {
this.suggestedContainer.style.display = suggestedFilters.length ? '' : 'none';
Array.from(this.suggestedContainer.children).slice(1).forEach(el => el.remove());
(suggestedFilters as DialogFilterSuggested[]).forEach(filter => {
const div = this.renderFolder(filter);
const button = document.createElement('button');
button.classList.add('btn-primary');
button.innerText = 'Add';
div.append(button);
this.suggestedContainer.append(div);
button.addEventListener('click', (e) => {
cancelEvent(e);
button.setAttribute('disabled', 'true');
appMessagesManager.filtersStorage.createDialogFilter(filter.filter).then(bool => {
if(bool) {
div.remove();
}
}).finally(() => {
button.removeAttribute('disabled');
});
});
});
});
}
onOpen() {
if(this.init) {
this.init();
this.init = null;
} else {
if(this.animation) {
this.animation.restart();
}
}
}
}

6
src/components/sidebarLeft/contacts.ts

@ -27,7 +27,7 @@ export default class AppContactsTab implements SliderTab { @@ -27,7 +27,7 @@ export default class AppContactsTab implements SliderTab {
this.container.firstElementChild.append(this.searchInput.container);
// preload contacts
appUsersManager.getContacts();
// appUsersManager.getContacts();
}
// need to clear, and left 1 page for smooth slide
@ -49,7 +49,7 @@ export default class AppContactsTab implements SliderTab { @@ -49,7 +49,7 @@ export default class AppContactsTab implements SliderTab {
if(this.promise) return this.promise;
this.scrollable.onScrolledBottom = null;
this.promise = appUsersManager.getContacts(query).then(contacts => {
this.promise = appUsersManager.getContacts(query).then(_contacts => {
this.promise = null;
if(appSidebarLeft.historyTabIDs[appSidebarLeft.historyTabIDs.length - 1] != AppSidebarLeft.SLIDERITEMSIDS.contacts) {
@ -57,7 +57,7 @@ export default class AppContactsTab implements SliderTab { @@ -57,7 +57,7 @@ export default class AppContactsTab implements SliderTab {
return;
}
contacts = contacts.slice();
const contacts = [..._contacts];
contacts.findAndSplice(u => u == $rootScope.myID);
let sorted = contacts

249
src/components/sidebarLeft/editFolder.ts

@ -0,0 +1,249 @@ @@ -0,0 +1,249 @@
import { SliderTab } from "../slider";
import appSidebarLeft, { AppSidebarLeft } from "../../lib/appManagers/appSidebarLeft";
import lottieLoader, { RLottiePlayer } from "../../lib/lottieLoader";
import appMessagesManager, { DialogFilter, Dialog } from "../../lib/appManagers/appMessagesManager";
import { parseMenuButtonsTo, ripple, toast } from "../misc";
import appDialogsManager from "../../lib/appManagers/appDialogsManager";
import { copy, deepEqual } from "../../lib/utils";
export default class AppEditFolderTab implements SliderTab {
public container: HTMLElement;
private closeBtn: HTMLElement;
private title: HTMLElement;
private caption: HTMLElement;
private stickerContainer: HTMLElement;
private confirmBtn: HTMLElement;
private menuBtn: HTMLElement;
private deleteFolderBtn: HTMLElement;
private nameInput: HTMLInputElement;
private include_peers: HTMLElement;
private exclude_peers: HTMLElement;
private flags: {[k in 'contacts' | 'non_contacts' | 'groups' | 'broadcasts' | 'bots' | 'exclude_muted' | 'exclude_archived' | 'exclude_read']: HTMLElement} = {} as any;
private animation: RLottiePlayer;
private filter: DialogFilter;
private originalFilter: DialogFilter;
private type: 'edit' | 'create';
init() {
this.container = document.querySelector('.edit-folder-container');
this.closeBtn = this.container.querySelector('.sidebar-close-button');
this.title = this.container.querySelector('.sidebar-header__title');
this.caption = this.container.querySelector('.caption');
this.stickerContainer = this.container.querySelector('.sticker-container');
this.confirmBtn = this.container.querySelector('.btn-confirm');
this.menuBtn = this.container.querySelector('.btn-menu-toggle');
this.deleteFolderBtn = this.menuBtn.querySelector('.menu-delete');
this.nameInput = this.container.querySelector('#folder-name');
this.include_peers = this.container.querySelector('.folder-list-included');
this.exclude_peers = this.container.querySelector('.folder-list-excluded');
const includedFlagsContainer = this.include_peers.querySelector('.folder-categories');
const excludedFlagsContainer = this.exclude_peers.querySelector('.folder-categories');
parseMenuButtonsTo(this.flags, includedFlagsContainer.children);
parseMenuButtonsTo(this.flags, excludedFlagsContainer.children);
includedFlagsContainer.firstElementChild.addEventListener('click', () => {
appSidebarLeft.includedChatsTab.open(this.filter, 'included');
});
excludedFlagsContainer.firstElementChild.addEventListener('click', () => {
appSidebarLeft.includedChatsTab.open(this.filter, 'excluded');
});
lottieLoader.loadAnimationFromURL({
container: this.stickerContainer,
loop: true,
autoplay: true,
width: 86,
height: 86
}, 'assets/img/Folders_2.tgs').then(player => {
this.animation = player;
});
this.deleteFolderBtn.addEventListener('click', () => {
this.deleteFolderBtn.setAttribute('disabled', 'true');
appMessagesManager.filtersStorage.updateDialogFilter(this.filter, true).then(bool => {
if(bool) {
this.closeBtn.click();
}
}).finally(() => {
this.deleteFolderBtn.removeAttribute('disabled');
});
});
this.confirmBtn.addEventListener('click', () => {
if(!this.nameInput.value.trim()) {
this.nameInput.classList.add('error');
return;
}
let include = (Array.from(includedFlagsContainer.children) as HTMLElement[]).slice(1).reduce((acc, el) => acc + +!el.style.display, 0);
if(this.include_peers.lastElementChild.tagName == 'UL') {
include += this.include_peers.lastElementChild.childElementCount;
}
if(!include) {
toast('Please choose at least one chat for this folder.');
return;
}
this.confirmBtn.setAttribute('disabled', 'true');
let promise: Promise<boolean>;
if(!this.filter.id) {
promise = appMessagesManager.filtersStorage.createDialogFilter(this.filter);
} else {
promise = appMessagesManager.filtersStorage.updateDialogFilter(this.filter);
}
promise.then(bool => {
if(bool) {
this.closeBtn.click();
}
}).finally(() => {
this.confirmBtn.removeAttribute('disabled');
});
});
this.nameInput.addEventListener('input', () => {
this.filter.title = this.nameInput.value;
this.nameInput.classList.remove('error');
this.editCheckForChange();
});
}
onOpen() {
if(this.init) {
this.init();
this.init = null;
} else {
if(this.animation) {
this.animation.restart();
}
}
}
onCloseAfterTimeout() {
Array.from(this.container.querySelectorAll('ul, .show-more')).forEach(el => el.remove());
}
private onCreateOpen() {
this.caption.style.display = '';
this.title.innerText = 'New Folder';
this.menuBtn.classList.add('hide');
this.confirmBtn.classList.remove('hide');
this.nameInput.value = '';
for(const flag in this.flags) {
// @ts-ignore
this.flags[flag].style.display = 'none';
}
}
private onEditOpen() {
this.caption.style.display = 'none';
this.title.innerText = this.type == 'create' ? 'New Folder' : 'Edit Folder';
if(this.type == 'edit') {
this.menuBtn.classList.remove('hide');
this.confirmBtn.classList.add('hide');
}
const filter = this.filter;
this.nameInput.value = filter.title;
for(const flag in this.flags) {
// @ts-ignore
this.flags[flag].style.display = !!filter.pFlags[flag] ? '' : 'none';
}
(['include_peers', 'exclude_peers'] as ['include_peers', 'exclude_peers']).forEach(key => {
const container = this[key];
const ul = document.createElement('ul');
const peers = filter[key].slice();
const renderMore = (_length: number) => {
for(let i = 0, length = Math.min(peers.length, _length); i < length; ++i) {
const peerID = peers.shift();
const {dom} = appDialogsManager.addDialog(peerID, ul, false, false, undefined, true);
dom.lastMessageSpan.parentElement.remove();
}
if(peers.length) {
showMore.innerHTML = `<div class="tgico-down"></div><div>Show ${Math.min(20, peers.length)} more chat${peers.length > 1 ? 's' : ''}</div>`;
} else if(showMore) {
showMore.remove();
}
};
container.append(ul);
let showMore: HTMLElement;
if(peers.length) {
showMore = document.createElement('div');
showMore.classList.add('show-more');
showMore.addEventListener('click', () => renderMore(20));
showMore.innerHTML = `<div class="tgico-down"></div><div>Show ${Math.min(20, peers.length)} more chat${peers.length > 1 ? 's' : ''}</div>`;
ripple(showMore);
container.append(showMore);
}
renderMore(4);
});
}
editCheckForChange() {
if(this.type == 'edit') {
const changed = !deepEqual(this.originalFilter, this.filter);
this.confirmBtn.classList.toggle('hide', !changed);
this.menuBtn.classList.toggle('hide', changed);
}
};
setFilter(filter: DialogFilter, firstTime: boolean) {
// cleanup
this.onCloseAfterTimeout();
if(firstTime) {
this.originalFilter = filter;
this.filter = copy(filter);
} else {
this.filter = filter;
this.onEditOpen();
this.editCheckForChange();
}
}
open(filter?: DialogFilter) {
appSidebarLeft.selectTab(AppSidebarLeft.SLIDERITEMSIDS.editFolder);
if(filter === undefined) {
this.setFilter({
_: 'dialogFilter',
flags: 0,
id: 0,
title: '',
pFlags: {},
pinned_peers: [],
include_peers: [],
exclude_peers: []
}, true);
this.type = 'create';
this.onCreateOpen();
} else {
this.setFilter(filter, true);
this.type = 'edit';
this.onEditOpen();
}
}
}

186
src/components/sidebarLeft/includedChats.ts

@ -0,0 +1,186 @@ @@ -0,0 +1,186 @@
import { SliderTab } from "../slider";
import { AppSelectPeers } from "../appSelectPeers";
import appSidebarLeft, { AppSidebarLeft } from "../../lib/appManagers/appSidebarLeft";
import appDialogsManager from "../../lib/appManagers/appDialogsManager";
import appPeersManager from "../../lib/appManagers/appPeersManager";
import appUsersManager from "../../lib/appManagers/appUsersManager";
import { $rootScope, copy, deepEqual } from "../../lib/utils";
import { DialogFilter } from "../../lib/appManagers/appMessagesManager";
export default class AppIncludedChatsTab implements SliderTab {
public container: HTMLElement;
private closeBtn: HTMLElement;
private confirmBtn: HTMLElement;
private title: HTMLElement;
private selector: AppSelectPeers;
private type: 'included' | 'excluded';
private filter: DialogFilter;
private originalFilter: DialogFilter;
init() {
this.container = document.querySelector('.included-chats-container');
this.closeBtn = this.container.querySelector('.sidebar-close-button');
this.confirmBtn = this.container.querySelector('.btn-confirm');
this.title = this.container.querySelector('.sidebar-header__title');
this.confirmBtn.addEventListener('click', () => {
const selected = this.selector.getSelected();
this.filter.pFlags = {};
const peers: number[] = [];
for(const key of selected) {
if(typeof(key) === 'number') {
peers.push(key);
} else {
// @ts-ignore
this.filter.pFlags[key] = true;
}
}
this.filter[this.type == 'included' ? 'include_peers' : 'exclude_peers'] = peers;
appSidebarLeft.editFolderTab.setFilter(this.filter, false);
this.closeBtn.click();
});
}
checkbox(selected?: boolean) {
return `<div class="checkbox"><label><input type="checkbox" ${selected ? 'checked' : ''}><span></span></label></div>`;
}
renderResults = (peerIDs: number[]) => {
const other = this.type == 'included' ? this.filter.exclude_peers : this.filter.include_peers;
peerIDs.forEach(peerID => {
if(other.includes(peerID)) return;
const {dom} = appDialogsManager.addDialog(peerID, this.selector.scrollable, false, false);
const selected = this.selector.selected.has(peerID);
dom.containerEl.insertAdjacentHTML('beforeend', this.checkbox(selected));
if(selected) dom.listEl.classList.add('active');
let subtitle = '';
if(peerID > 0) {
if(peerID == $rootScope.myID) {
subtitle = 'Chat with yourself';
} else if(appUsersManager.isBot(peerID)) {
subtitle = 'Bot';
} else {
subtitle = appUsersManager.contactsList.has(peerID) ? 'Contact' : 'Non-Contact';
}
} else {
subtitle = appPeersManager.isBroadcast(peerID) ? 'Channel' : 'Group';
}
dom.lastMessageSpan.innerHTML = subtitle;
});
};
onOpen() {
if(this.init) {
this.init();
this.init = null;
}
this.confirmBtn.style.display = this.type == 'excluded' ? '' : 'none';
this.title.innerText = this.type == 'included' ? 'Included Chats' : 'Excluded Chats';
const filter = this.filter;
const fragment = document.createDocumentFragment();
const dd = document.createElement('div');
dd.classList.add('sidebar-left-h2');
dd.innerText = 'Chat types';
const categories = document.createElement('div');
categories.classList.add('folder-categories');
let details: any;
if(this.type == 'excluded') {
details = {
exclude_muted: {ico: 'tgico-mute', text: 'Muted'},
exclude_archived: {ico: 'tgico-archive', text: 'Archived'},
exclude_read: {ico: 'tgico-readchats', text: 'Read'}
};
} else {
details = {
contacts: {ico: 'tgico-newprivate', text: 'Contacts'},
non_contacts: {ico: 'tgico-noncontacts', text: 'Non-Contacts'},
groups: {ico: 'tgico-group', text: 'Groups'},
broadcasts: {ico: 'tgico-newchannel', text: 'Channels'},
bots: {ico: 'tgico-bots', text: 'Bots'}
};
}
let html = '';
for(const key in details) {
html += `<div class="folder-category-button ${details[key].ico}" data-peerID="${key}"><p>${details[key].text}</p>${this.checkbox()}</div>`;
}
categories.innerHTML = html;
const hr = document.createElement('hr');
hr.style.margin = '7px 0 9px';
const d = document.createElement('div');
d.classList.add('sidebar-left-h2');
d.innerText = 'Chats';
fragment.append(dd, categories, hr, d);
/////////////////
const selectedPeers = (this.type == 'included' ? filter.include_peers : filter.exclude_peers).slice();
this.selector = new AppSelectPeers(this.container, this.onSelectChange, 'dialogs', null, this.renderResults);
this.selector.selected = new Set(selectedPeers);
this.selector.input.placeholder = 'Search';
const _add = this.selector.add.bind(this.selector);
this.selector.add = (peerID, title) => {
const div = _add(peerID, details[peerID]?.text);
if(details[peerID]) {
div.querySelector('avatar-element').classList.add(details[peerID].ico);
}
return div;
};
this.selector.list.parentElement.insertBefore(fragment, this.selector.list);
selectedPeers.forEach(peerID => {
this.selector.add(peerID);
});
for(const flag in filter.pFlags) {
// @ts-ignore
if(details.hasOwnProperty(flag) && !!filter.pFlags[flag]) {
(categories.querySelector(`[data-peerID="${flag}"]`) as HTMLElement).click();
}
}
}
onSelectChange = (length: number) => {
//const changed = !deepEqual(this.filter, this.originalFilter);
if(this.type == 'included') {
this.confirmBtn.style.display = length ? '' : 'none';
}
};
onCloseAfterTimeout() {
if(this.selector) {
this.selector.container.remove();
this.selector = null;
}
}
open(filter: DialogFilter, type: 'included' | 'excluded') {
this.originalFilter = filter;
this.filter = copy(this.originalFilter);
this.type = type;
appSidebarLeft.selectTab(AppSidebarLeft.SLIDERITEMSIDS.includedChats);
}
}

7
src/components/sidebarLeft/settings.ts

@ -16,6 +16,7 @@ export default class AppSettingsTab implements SliderTab { @@ -16,6 +16,7 @@ export default class AppSettingsTab implements SliderTab {
private buttons: {
edit: HTMLButtonElement,
folders: HTMLButtonElement,
general: HTMLButtonElement,
notifications: HTMLButtonElement,
privacy: HTMLButtonElement,
@ -34,8 +35,12 @@ export default class AppSettingsTab implements SliderTab { @@ -34,8 +35,12 @@ export default class AppSettingsTab implements SliderTab {
});
this.buttons.edit.addEventListener('click', () => {
appSidebarLeft.selectTab(AppSidebarLeft.SLIDERITEMSIDS.editProfile);
appSidebarLeft.editProfileTab.fillElements();
appSidebarLeft.selectTab(AppSidebarLeft.SLIDERITEMSIDS.editProfile);
});
this.buttons.folders.addEventListener('click', () => {
appSidebarLeft.selectTab(AppSidebarLeft.SLIDERITEMSIDS.chatFolders);
});
}

26
src/components/slider.ts

@ -1,16 +1,20 @@ @@ -1,16 +1,20 @@
import { horizontalMenu } from "./misc";
export interface SliderTab {
onOpen?: () => void,
onOpenAfterTimeout?: () => void,
onClose?: () => void,
onCloseAfterTimeout?: () => void
}
const TRANSITIONTIME = 420;
export default class SidebarSlider {
protected _selectTab: (id: number) => void;
public historyTabIDs: number[] = [];
constructor(public sidebarEl: HTMLElement, public tabs: {[id: number]: SliderTab}) {
this._selectTab = horizontalMenu(null, this.sidebarEl.querySelector('.sidebar-slider') as HTMLDivElement, null, null, 420);
this._selectTab = horizontalMenu(null, this.sidebarEl.querySelector('.sidebar-slider') as HTMLDivElement, null, null, TRANSITIONTIME);
this._selectTab(0);
let onCloseBtnClick = () => {
@ -28,6 +32,20 @@ export default class SidebarSlider { @@ -28,6 +32,20 @@ export default class SidebarSlider {
if(this.historyTabIDs[this.historyTabIDs.length - 1] == id) {
return;
}
const tab = this.tabs[id];
if(tab) {
if(tab.onOpen) {
tab.onOpen();
}
if(tab.onOpenAfterTimeout) {
setTimeout(() => {
tab.onOpenAfterTimeout();
}, TRANSITIONTIME);
}
}
this.historyTabIDs.push(id);
this._selectTab(id);
@ -41,14 +59,14 @@ export default class SidebarSlider { @@ -41,14 +59,14 @@ export default class SidebarSlider {
public onCloseTab(id: number) {
let tab = this.tabs[id];
if(tab) {
if('onClose' in tab) {
if(tab.onClose) {
tab.onClose();
}
if('onCloseAfterTimeout' in tab) {
if(tab.onCloseAfterTimeout) {
setTimeout(() => {
tab.onCloseAfterTimeout();
}, 420);
}, TRANSITIONTIME);
}
}
}

92
src/index.hbs

@ -210,7 +210,21 @@ @@ -210,7 +210,21 @@
</div>
</div>
<div class="sidebar-content">
<div id="chats-container"><ul id="dialogs"></ul></div>
<div id="chats-container">
<div class="folders-tabs-scrollable">
<nav class="menu-horizontal" style="display: none;" id="folders-tabs">
<ul>
<li class="rp"><span>All</span></li>
</ul>
</nav>
</div>
{{!-- <div class="tabs-container" id="folders-container" data-slider="none"> --}}
<div class="tabs-container" id="folders-container">
<div>
<ul id="dialogs"></ul>
</div>
</div>
</div>
<div class="sidebar-search hide" id="search-container"></div>
<button class="btn-primary btn-circle btn-icon rp btn-corner tgico-newchat_filled btn-menu-toggle" id="new-menu">
<div class="btn-menu top-left">
@ -311,6 +325,7 @@ @@ -311,6 +325,7 @@
<div class="profile-subtitle"></div>
<div class="profile-buttons">
<div class="profile-button menu-edit tgico-edit rp"><p>Edit Profile</p></div>
<div class="profile-button menu-folders tgico-folder rp"><p>Chat Folders</p></div>
<div class="profile-button menu-general tgico-settings rp"><p>General Settings</p></div>
<div class="profile-button menu-notifications tgico-unmute rp"><p>Notifications</p></div>
<div class="profile-button menu-privacy tgico-lock rp"><p>Privacy and Security</p></div>
@ -360,6 +375,81 @@ @@ -360,6 +375,81 @@
<button class="btn-primary btn-circle btn-icon rp btn-corner tgico-check"></button>
</div>
</div>
<div class="sidebar-slider-item chat-folders-container">
<div class="sidebar-header">
<button class="btn-icon rp tgico-back sidebar-close-button"></button>
<div class="sidebar-header__title">Chat Folders</div>
</div>
<div class="sidebar-content">
<div class="chat-folders scrollable scrollable-y">
<div class="sticker-container"></div>
<div class="caption">Create folders for different groups of chats<br>and quickly switch between them.</div>
<button class="btn-primary btn-create-folder rp">
<div class="tgico-add"></div>
Create Folder
</button>
<hr/>
<div class="folders-container folders-my">
<div class="sidebar-left-h2">Folders</div>
</div>
<hr/>
<div class="folders-container folders-suggested" style="display: none;">
<div class="sidebar-left-h2">Recommended folders</div>
</div>
</div>
</div>
</div>
<div class="sidebar-slider-item edit-folder-container">
<div class="sidebar-header">
<button class="btn-icon rp tgico-back sidebar-close-button"></button>
<div class="sidebar-header__title"></div>
<div class="btn-icon tgico-check1 btn-confirm rp hide"></div>
<div class="btn-icon btn-menu-toggle rp tgico-more hide">
<div class="btn-menu bottom-left">
<div class="btn-menu-item menu-delete tgico-delete danger rp">Delete Folder</div>
</div>
</div>
</div>
<div class="sidebar-content">
<div class="edit-folder scrollable scrollable-y">
<div class="sticker-container"></div>
<div class="caption">Choose chats and types of chats that will<br>appear and never appear in this folder.</div>
<div class="input-wrapper">
<div class="input-field">
<input type="text" name="folder-name" class="folder-name" autocomplete="aintCCZsofunnowhHQ" id="folder-name" required="">
<label for="folder-name">Folder Name</label>
</div>
</div>
<div class="folder-list folder-list-included">
<div class="sidebar-left-h2">Included chats</div>
<div class="folder-categories">
<div class="folder-category-button blue tgico-add rp"><p>Add Chats</p></div>
<div class="folder-category-button menu-contacts tgico-group"><p>Contacts</p></div>
<div class="folder-category-button menu-non_contacts tgico-noncontacts"><p>Non-Contacts</p></div>
<div class="folder-category-button menu-groups tgico-group"><p>Groups</p></div>
<div class="folder-category-button menu-broadcasts tgico-channel"><p>Channels</p></div>
<div class="folder-category-button menu-bots tgico-bots"><p>Bots</p></div>
</div>
</div>
<div class="folder-list folder-list-excluded">
<div class="sidebar-left-h2">Excluded chats</div>
<div class="folder-categories">
<div class="folder-category-button blue tgico-minus rp"><p>Remove Chats</p></div>
<div class="folder-category-button menu-exclude_muted tgico-mute"><p>Muted</p></div>
<div class="folder-category-button menu-exclude_archived tgico-archive"><p>Archived</p></div>
<div class="folder-category-button menu-exclude_read tgico-readchats"><p>Read</p></div>
</div>
</div>
</div>
</div>
</div>
<div class="sidebar-slider-item included-chats-container">
<div class="sidebar-header">
<button class="btn-icon rp tgico-back sidebar-close-button"></button>
<div class="sidebar-header__title"></div>
<div class="btn-icon tgico-check1 btn-confirm rp" style="display: none;"></div>
</div>
</div>
</div>
<div class="btn-menu" id="dialogs-contextmenu">
<div class="btn-menu-item menu-unread tgico rp"></div>

689
src/lib/appManagers/appDialogsManager.ts

@ -1,16 +1,17 @@ @@ -1,16 +1,17 @@
import { findUpClassName, $rootScope, escapeRegExp, whichChild, findUpTag, cancelEvent, formatNumber } from "../utils";
import appImManager, { AppImManager } from "./appImManager";
import appPeersManager from './appPeersManager';
import appMessagesManager, { AppMessagesManager, Dialog } from "./appMessagesManager";
import appMessagesManager, { AppMessagesManager, Dialog, DialogFilter } from "./appMessagesManager";
import appUsersManager, { User } from "./appUsersManager";
import { RichTextProcessor } from "../richtextprocessor";
import { ripple, putPreloader, positionMenu, openBtnMenu, parseMenuButtonsTo } from "../../components/misc";
import { ripple, putPreloader, positionMenu, openBtnMenu, parseMenuButtonsTo, horizontalMenu } from "../../components/misc";
//import Scrollable from "../../components/scrollable";
import Scrollable from "../../components/scrollable_new";
import { logger } from "../polyfill";
import { logger, LogLevels } from "../polyfill";
import appChatsManager from "./appChatsManager";
import AvatarElement from "../../components/avatar";
import { PopupButton, PopupPeer } from "../../components/popup";
import { SliderTab } from "../../components/slider";
type DialogDom = {
avatarEl: AvatarElement,
@ -39,106 +40,11 @@ class DialogsContextMenu { @@ -39,106 +40,11 @@ class DialogsContextMenu {
} = {} as any;
private selectedID: number;
private peerType: 'channel' | 'chat' | 'megagroup' | 'group' | 'saved';
private filterID: number;
constructor(private attachTo: HTMLElement[]) {
constructor() {
parseMenuButtonsTo(this.buttons, this.element.children);
const onContextMenu = (e: MouseEvent) => {
let li: HTMLDivElement = null;
try {
li = findUpTag(e.target, 'LI');
} catch(e) {}
if(!li) return;
e.preventDefault();
if(this.element.classList.contains('active')) {
return false;
}
e.cancelBubble = true;
this.selectedID = +li.getAttribute('data-peerID');
const dialog = appMessagesManager.getDialogByPeerID(this.selectedID)[0];
const notOurDialog = dialog.peerID != $rootScope.myID;
// archive button
if(notOurDialog) {
const button = this.buttons.archive;
const condition = dialog.folder_id == 1;
button.classList.toggle('flip-icon', condition);
button.innerText = condition ? 'Unarchive' : 'Archive';
this.buttons.archive.style.display = '';
} else {
this.buttons.archive.style.display = 'none';
}
// pin button
{
const button = this.buttons.pin;
const condition = dialog.pFlags?.pinned;
button.classList.toggle('flip-icon', condition);
button.innerText = condition ? 'Unpin' : 'Pin';
}
// mute button
if(notOurDialog) {
const button = this.buttons.mute;
const condition = dialog.notify_settings && dialog.notify_settings.mute_until > (Date.now() / 1000 | 0);
button.classList.toggle('flip-icon', condition);
button.innerText = condition ? 'Unmute' : 'Mute';
this.buttons.mute.style.display = '';
} else {
this.buttons.mute.style.display = 'none';
}
// unread button
{
const button = this.buttons.unread;
const condition = !!(dialog.pFlags?.unread_mark || dialog.unread_count);
button.classList.toggle('flip-icon', condition);
button.innerText = condition ? 'Mark as Read' : 'Mark as Unread';
}
/* // clear history button
if(appPeersManager.isChannel(this.selectedID)) {
this.buttons.clear.style.display = 'none';
} else {
this.buttons.clear.style.display = '';
} */
// delete button
let deleteButtonText = '';
if(appPeersManager.isMegagroup(this.selectedID)) {
deleteButtonText = 'Leave';
//deleteButtonText = 'Leave group';
this.peerType = 'megagroup';
} else if(appPeersManager.isChannel(this.selectedID)) {
deleteButtonText = 'Leave';
//deleteButtonText = 'Leave channel';
this.peerType = 'channel';
} else if(this.selectedID < 0) {
deleteButtonText = 'Delete';
//deleteButtonText = 'Delete and leave';
this.peerType = 'group';
} else {
deleteButtonText = 'Delete';
//deleteButtonText = 'Delete chat';
this.peerType = this.selectedID == $rootScope.myID ? 'saved' : 'chat';
}
this.buttons.delete.innerText = deleteButtonText;
li.classList.add('menu-open');
positionMenu(e, this.element);
openBtnMenu(this.element, () => {
li.classList.remove('menu-open');
});
};
this.attachTo.forEach(el => {
el.addEventListener('contextmenu', onContextMenu);
});
this.buttons.archive.addEventListener('click', () => {
let dialog = appMessagesManager.getDialogByPeerID(this.selectedID)[0];
if(dialog) {
@ -147,7 +53,7 @@ class DialogsContextMenu { @@ -147,7 +53,7 @@ class DialogsContextMenu {
});
this.buttons.pin.addEventListener('click', () => {
appMessagesManager.toggleDialogPin(this.selectedID);
appMessagesManager.toggleDialogPin(this.selectedID, this.filterID);
});
this.buttons.mute.addEventListener('click', () => {
@ -255,11 +161,159 @@ class DialogsContextMenu { @@ -255,11 +161,159 @@ class DialogsContextMenu {
popup.show();
});
}
onContextMenu = (e: MouseEvent) => {
let li: HTMLDivElement = null;
try {
li = findUpTag(e.target, 'LI');
} catch(e) {}
if(!li) return;
e.preventDefault();
if(this.element.classList.contains('active')) {
return false;
}
e.cancelBubble = true;
this.filterID = appDialogsManager.filterID;
this.selectedID = +li.getAttribute('data-peerID');
const dialog = appMessagesManager.getDialogByPeerID(this.selectedID)[0];
const notOurDialog = dialog.peerID != $rootScope.myID;
// archive button
if(notOurDialog) {
const button = this.buttons.archive;
const condition = dialog.folder_id == 1;
button.classList.toggle('flip-icon', condition);
button.innerText = condition ? 'Unarchive' : 'Archive';
this.buttons.archive.style.display = '';
} else {
this.buttons.archive.style.display = 'none';
}
// pin button
{
const button = this.buttons.pin;
//const condition = !!dialog.pFlags?.pinned;
const condition = this.filterID > 1 ? appMessagesManager.filtersStorage.filters[this.filterID].pinned_peers.includes(dialog.peerID) : !!dialog.pFlags?.pinned;
button.classList.toggle('flip-icon', condition);
button.innerText = condition ? 'Unpin' : 'Pin';
}
// mute button
if(notOurDialog) {
const button = this.buttons.mute;
const condition = dialog.notify_settings && dialog.notify_settings.mute_until > (Date.now() / 1000 | 0);
button.classList.toggle('flip-icon', condition);
button.innerText = condition ? 'Unmute' : 'Mute';
this.buttons.mute.style.display = '';
} else {
this.buttons.mute.style.display = 'none';
}
// unread button
{
const button = this.buttons.unread;
const condition = !!(dialog.pFlags?.unread_mark || dialog.unread_count);
button.classList.toggle('flip-icon', condition);
button.innerText = condition ? 'Mark as Read' : 'Mark as Unread';
}
/* // clear history button
if(appPeersManager.isChannel(this.selectedID)) {
this.buttons.clear.style.display = 'none';
} else {
this.buttons.clear.style.display = '';
} */
// delete button
let deleteButtonText = '';
if(appPeersManager.isMegagroup(this.selectedID)) {
deleteButtonText = 'Leave';
//deleteButtonText = 'Leave group';
this.peerType = 'megagroup';
} else if(appPeersManager.isChannel(this.selectedID)) {
deleteButtonText = 'Leave';
//deleteButtonText = 'Leave channel';
this.peerType = 'channel';
} else if(this.selectedID < 0) {
deleteButtonText = 'Delete';
//deleteButtonText = 'Delete and leave';
this.peerType = 'group';
} else {
deleteButtonText = 'Delete';
//deleteButtonText = 'Delete chat';
this.peerType = this.selectedID == $rootScope.myID ? 'saved' : 'chat';
}
this.buttons.delete.innerText = deleteButtonText;
li.classList.add('menu-open');
positionMenu(e, this.element);
openBtnMenu(this.element, () => {
li.classList.remove('menu-open');
});
};
}
export class AppArchivedTab implements SliderTab {
public container = document.getElementById('chats-archived-container') as HTMLDivElement;
public chatList = document.getElementById('dialogs-archived') as HTMLUListElement;
public scroll: Scrollable = null;
public loadedAll: boolean;
public loadDialogsPromise: Promise<any>;
public wasFilterID: number;
init() {
this.scroll = new Scrollable(this.container, 'y', 'CLA', this.chatList, 500);
this.scroll.setVirtualContainer(this.chatList);
this.scroll.onScrolledBottom = appDialogsManager.onChatsScroll;
///this.scroll.attachSentinels();
appDialogsManager.setListClickListener(this.chatList);
this.chatList.addEventListener('contextmenu', appDialogsManager.contextMenu.onContextMenu);
window.addEventListener('resize', () => {
setTimeout(appDialogsManager.onChatsScroll, 0);
});
}
onOpen() {
if(this.init) {
this.init();
this.init = null;
}
this.wasFilterID = appDialogsManager.filterID;
appDialogsManager.scroll = this.scroll;
appDialogsManager.filterID = 1;
appDialogsManager.onTabChange();
}
// вообще, так делать нельзя, но нет времени чтобы переделать главный чатлист на слайд...
onOpenAfterTimeout() {
appDialogsManager.chatLists[this.wasFilterID].innerHTML = '';
}
onClose() {
appDialogsManager.scroll = appDialogsManager._scroll;
appDialogsManager.filterID = this.wasFilterID;
appDialogsManager.onTabChange();
}
onCloseAfterTimeout() {
this.chatList.innerHTML = '';
}
}
export const archivedTab = new AppArchivedTab();
export class AppDialogsManager {
public chatList = document.getElementById('dialogs') as HTMLUListElement;
public chatListArchived = document.getElementById('dialogs-archived') as HTMLUListElement;
public _chatList = document.getElementById('dialogs') as HTMLUListElement;
public chatList = this._chatList;
public pinnedDelimiter: HTMLDivElement;
/* public chatsHidden: Scrollable["hiddenElements"];
public chatsVisible: Scrollable["visibleElements"];
@ -267,51 +321,58 @@ export class AppDialogsManager { @@ -267,51 +321,58 @@ export class AppDialogsManager {
public chatsArchivedVisible: Scrollable["visibleElements"]; */
public doms: {[peerID: number]: DialogDom} = {};
public domsArchived: {[peerID: number]: DialogDom} = {};
public lastActiveListElement: HTMLElement = null;
/* private rippleCallback: (value?: boolean | PromiseLike<boolean>) => void = null;
private lastClickID = 0;
private lastGoodClickID = 0; */
public chatsArchivedContainer = document.getElementById('chats-archived-container') as HTMLDivElement;
public chatsContainer = document.getElementById('chats-container') as HTMLDivElement;
private chatsPreloader: HTMLDivElement;
//private chatsLoadCount = 0;
//private loadDialogsPromise: Promise<any>;
private loadDialogsPromise: ReturnType<AppMessagesManager["getConversations"]>;
private loadedAll = false;
private loadedArchivedAll = false;
public loadDialogsPromise: ReturnType<AppMessagesManager["getConversations"]>;
public loadedAll = false;
public scroll: Scrollable = null;
public scrollArchived: Scrollable = null;
private log = logger('DIALOGS');
private contextMenu = new DialogsContextMenu([this.chatList, this.chatListArchived]);
public _scroll: Scrollable = null;
private log = logger('DIALOGS', LogLevels.log | LogLevels.error | LogLevels.warn | LogLevels.debug);
public contextMenu = new DialogsContextMenu();
public chatLists: {[filterID: number]: HTMLUListElement} = {
0: this.chatList,
1: archivedTab.chatList
};
public filterID = 0;
private folders: {[k in 'menu' | 'container']: HTMLElement} = {
menu: document.getElementById('folders-tabs'),
container: document.getElementById('folders-container')
};
private filtersRendered: {
[filterID: string]: {
menu: HTMLElement, container: HTMLElement
}
} = {};
private debug = false;
private accumulateArchivedTimeout: number;
constructor() {
this.chatList.addEventListener('contextmenu', this.contextMenu.onContextMenu);
this.chatsPreloader = putPreloader(null, true);
this.pinnedDelimiter = document.createElement('div');
this.pinnedDelimiter.classList.add('pinned-delimiter');
this.pinnedDelimiter.appendChild(document.createElement('span'));
if(USEPINNEDDELIMITER) {
this.pinnedDelimiter = document.createElement('div');
this.pinnedDelimiter.classList.add('pinned-delimiter');
this.pinnedDelimiter.appendChild(document.createElement('span'));
}
this.scroll = new Scrollable(this.chatsContainer, 'y', 'CL', this.chatList, 500);
this.scroll = this._scroll = new Scrollable(this.chatsContainer, 'y', 'CL', this.chatList, 500);
this.scroll.onScrolledBottom = this.onChatsScroll;
this.scroll.setVirtualContainer(this.chatList);
this.scroll.onScrolledBottom = this.onChatsScroll.bind(this);
//this.scroll.attachSentinels();
this.scrollArchived = new Scrollable(this.chatsArchivedContainer, 'y', 'CLA', this.chatListArchived, 500);
this.scrollArchived.setVirtualContainer(this.chatListArchived);
this.scrollArchived.onScrolledBottom = this.onChatsArchivedScroll.bind(this);
///this.scroll.attachSentinels();
this.setListClickListener(this.chatList);
this.setListClickListener(this.chatListArchived);
if(testScroll) {
let i = 0;
@ -329,14 +390,6 @@ export class AppDialogsManager { @@ -329,14 +390,6 @@ export class AppDialogsManager {
(window as any).addElement = add;
}
window.addEventListener('resize', () => {
//this.chatsLoadCount = Math.round(document.body.scrollHeight / 70 * 1.5);
setTimeout(() => {
this.onChatsArchivedScroll();
}, 0);
});
$rootScope.$on('user_update', (e: CustomEvent) => {
let userID = e.detail;
@ -377,26 +430,20 @@ export class AppDialogsManager { @@ -377,26 +430,20 @@ export class AppDialogsManager {
let dialog = appMessagesManager.getDialogByPeerID(peerID)[0];
if(dialog) {
this.setLastMessage(dialog);
this.validateForFilter();
}
});
$rootScope.$on('dialogs_multiupdate', (e: CustomEvent) => {
let dialogs = e.detail;
for(let id in dialogs) {
let dialog = dialogs[id];
const dialogs = e.detail;
/////console.log('updating dialog:', dialog);
if(!(dialog.peerID in this.doms)) {
this.addDialog(dialog);
}
this.setLastMessage(dialog);
this.setDialogPosition(dialog);
for(const id in dialogs) {
const dialog = dialogs[id];
this.updateDialog(dialog);
}
this.setPinnedDelimiter();
this.validateForFilter();
});
$rootScope.$on('dialog_drop', (e: CustomEvent) => {
@ -406,7 +453,7 @@ export class AppDialogsManager { @@ -406,7 +453,7 @@ export class AppDialogsManager {
if(dom) {
dom.listEl.remove();
delete this.doms[peerID];
(dialog.folder_id == 1 ? this.scrollArchived : this.scroll).reorder();
this.scroll.reorder();
}
});
@ -423,6 +470,8 @@ export class AppDialogsManager { @@ -423,6 +470,8 @@ export class AppDialogsManager {
if(dialog.peerID == $rootScope.selectedPeerID) {
appImManager.updateUnreadByDialog(dialog);
}
this.validateForFilter();
}
});
@ -444,29 +493,190 @@ export class AppDialogsManager { @@ -444,29 +493,190 @@ export class AppDialogsManager {
}
});
$rootScope.$on('filter_update', (e: CustomEvent) => {
const filter: DialogFilter = e.detail;
if(!this.filtersRendered[filter.id]) {
this.addFilter(filter);
} else if(filter.id == this.filterID) { // это нет тут смысла вызывать, так как будет dialogs_multiupdate
//this.validateForFilter();
const folder = appMessagesManager.dialogsStorage.getFolder(filter.id);
this.validateForFilter();
for(let i = 0, length = folder.length; i < length; ++i) {
const dialog = folder[i];
this.updateDialog(dialog);
}
}
});
$rootScope.$on('filter_delete', (e: CustomEvent) => {
const filter: DialogFilter = e.detail;
const elements = this.filtersRendered[filter.id];
if(!elements) return;
// set tab
//(this.folders.menu.firstElementChild.children[Math.max(0, filter.id - 2)] as HTMLElement).click();
(this.folders.menu.firstElementChild.children[0] as HTMLElement).click();
elements.container.remove();
elements.menu.remove();
delete this.chatLists[filter.id];
delete this.filtersRendered[filter.id];
if(!Object.keys(this.filtersRendered).length) {
this.folders.menu.style.display = 'none';
}
});
/* $rootScope.$on('filter_pinned_order', (e: CustomEvent) => {
const {order, id} = e.detail as {order: number[], id: number};
if(this.prevTabID != id) {
return;
}
for(const peerID of order) {
this.updateDialog(appMessagesManager.getDialogByPeerID(peerID)[0]);
}
}); */
new Scrollable(this.folders.menu.parentElement, 'x');
const selectTab = horizontalMenu(this.folders.menu, this.folders.container, (id, tabContent) => {
/* if(id != 0) {
id += 1;
} */
id = +tabContent.dataset.filterID || 0;
if(this.filterID == id) return;
this.scroll.setVirtualContainer(this.chatLists[id]);
this.filterID = id;
this.onTabChange();
}, () => {
for(const folderID in this.chatLists) {
if(+folderID != this.filterID) {
this.chatLists[folderID].innerHTML = '';
}
}
});
//selectTab(0);
(this.folders.menu.firstElementChild.firstElementChild as HTMLElement).click();
/* false && */appMessagesManager.loadSavedState().then(() => {
this.loadDialogs().then(result => {
this.setPinnedDelimiter();
//appSidebarLeft.onChatsScroll();
this.loadDialogs(true);
});
return appMessagesManager.filtersStorage.getDialogFilters();
}).then(filters => {
for(const filterID in filters) {
this.addFilter(filters[filterID]);
}
return this.loadDialogs(this.filterID);
}).then(result => {
this.setPinnedDelimiter();
//appSidebarLeft.onChatsScroll();
this.loadDialogs(1);
});
}
public async loadDialogs(archived = false) {
private updateDialog(dialog: Dialog) {
if(!dialog) {
return;
}
if(!this.doms.hasOwnProperty(dialog.peerID)) {
this.addDialog(dialog);
}
if(this.getDialogDom(dialog.peerID)) {
this.setLastMessage(dialog);
this.setDialogPosition(dialog);
}
}
onTabChange = () => {
this.doms = {};
this.loadedAll = false;
this.lastActiveListElement = null;
this.chatList = this.chatLists[this.filterID];
this.loadDialogs(this.filterID);
};
/**
* Удалит неподходящие чаты из списка, но не добавит их(!)
*/
public validateForFilter() {
if(this.filterID == 0) return;
const folder = appMessagesManager.dialogsStorage.getFolder(this.filterID);
let affected = false;
for(const _peerID in this.doms) {
const peerID = +_peerID;
// если больше не подходит по фильтру, удаляем
if(folder.findIndex((dialog) => dialog.peerID == peerID) === -1) {
const listEl = this.doms[peerID].listEl;
listEl.remove();
affected = true;
if(this.lastActiveListElement == listEl) {
this.lastActiveListElement = null;
}
}
}
if(affected) {
this.scroll.reorder();
}
}
public addFilter(filter: DialogFilter) {
if(this.filtersRendered[filter.id]) return;
const li = document.createElement('li');
const span = document.createElement('span');
span.innerHTML = RichTextProcessor.wrapEmojiText(filter.title);
li.append(span);
ripple(li);
this.folders.menu.firstElementChild.append(li);
const ul = document.createElement('ul');
const div = document.createElement('div');
div.append(ul);
div.dataset.filterID = '' + filter.id;
this.folders.container.append(div);
this.chatLists[filter.id] = ul;
this.setListClickListener(ul);
ul.addEventListener('contextmenu', this.contextMenu.onContextMenu);
setTimeout(() => {
this.folders.menu.style.display = '';
}, 0);
this.filtersRendered[filter.id] = {
menu: li,
container: div
};
}
public async loadDialogs(folderID: number) {
if(testScroll) {
return;
}
if(this.loadDialogsPromise/* || 1 == 1 */) return this.loadDialogsPromise;
(archived ? this.chatsArchivedContainer : this.chatsContainer).append(this.chatsPreloader);
if(!this.chatList.childElementCount) {
const container = this.chatList.parentElement;
container.append(this.chatsPreloader);
}
let storage = appMessagesManager.dialogsStorage[+archived] || [];
const storage = appMessagesManager.dialogsStorage.getFolder(folderID);
let offsetIndex = 0;
for(let i = storage.length - 1; i >= 0; --i) {
let dialog = storage[i];
const dialog = storage[i];
if(this.getDialogDom(dialog.peerID)) {
offsetIndex = dialog.index;
break;
@ -477,10 +687,10 @@ export class AppDialogsManager { @@ -477,10 +687,10 @@ export class AppDialogsManager {
try {
//console.time('getDialogs time');
let loadCount = 50/*this.chatsLoadCount */;
this.loadDialogsPromise = appMessagesManager.getConversations('', offsetIndex, loadCount, +archived);
const loadCount = 50/*this.chatsLoadCount */;
this.loadDialogsPromise = appMessagesManager.getConversations('', offsetIndex, loadCount, folderID);
let result = await this.loadDialogsPromise;
const result = await this.loadDialogsPromise;
//console.timeEnd('getDialogs time');
@ -490,12 +700,11 @@ export class AppDialogsManager { @@ -490,12 +700,11 @@ export class AppDialogsManager {
});
}
if(!result.dialogs.length || (archived ? this.scrollArchived.length == result.count : this.scroll.length == result.count)) { // loaded all
if(archived) this.loadedArchivedAll = true;
else this.loadedAll = true;
if(!result.dialogs.length || this.chatList.childElementCount == result.count) { // loaded all
this.loadedAll = true;
}
this.debug && this.log('getDialogs ' + loadCount + ' dialogs by offset:', offsetIndex, result, this.scroll.length, archived);
this.log.debug('getDialogs ' + loadCount + ' dialogs by offset:', offsetIndex, result, this.chatList.childElementCount);
this.scroll.onScroll();
} catch(err) {
this.log.error(err);
@ -505,18 +714,10 @@ export class AppDialogsManager { @@ -505,18 +714,10 @@ export class AppDialogsManager {
this.loadDialogsPromise = undefined;
}
public onChatsScroll() {
onChatsScroll = () => {
if(this.loadedAll || this.loadDialogsPromise) return;
this.log('onChatsScroll');
this.loadDialogs();
}
public onChatsArchivedScroll() {
if(this.loadedArchivedAll || this.loadDialogsPromise) return;
this.loadDialogs(true);
this.loadDialogs(this.filterID);
}
public setListClickListener(list: HTMLUListElement, onFound?: () => void) {
@ -564,13 +765,21 @@ export class AppDialogsManager { @@ -564,13 +765,21 @@ export class AppDialogsManager {
}, {capture: true});
}
public setDialogPosition(dialog: Dialog) {
let pos = appMessagesManager.getDialogByPeerID(dialog.peerID)[1];
let dom = this.getDialogDom(dialog.peerID);
public setDialogPosition(dialog: Dialog, pos?: number) {
const dom = this.getDialogDom(dialog.peerID);
if(!dom) {
return;
}
if(pos === undefined) {
pos = appMessagesManager.dialogsStorage.getDialog(dialog.peerID, this.filterID)[1];
}
let prevPos = whichChild(dom.listEl);
let wrongFolder = (dialog.folder_id == 1 && this.chatList == dom.listEl.parentElement) || (dialog.folder_id == 0 && this.chatListArchived == dom.listEl.parentElement);
if(wrongFolder) prevPos = 0xFFFF;
/* let wrongFolder = (dialog.folder_id == 1 && this.chatList == dom.listEl.parentElement) || (dialog.folder_id == 0 && this.chatListArchived == dom.listEl.parentElement);
let wrongFolder = false;
if(wrongFolder) prevPos = 0xFFFF; */
if(prevPos == pos) {
return;
@ -578,23 +787,23 @@ export class AppDialogsManager { @@ -578,23 +787,23 @@ export class AppDialogsManager {
pos += 1;
}
let chatList = dialog.folder_id == 1 ? this.chatListArchived : this.chatList;
const chatList = this.chatList;
if(chatList.childElementCount > pos) {
chatList.insertBefore(dom.listEl, chatList.children[pos]);
} else {
chatList.append(dom.listEl);
}
(dialog.folder_id == 1 ? this.scrollArchived : this.scroll).reorder();
this.scroll.reorder();
this.debug && this.log('setDialogPosition:', dialog, dom, pos);
this.log.debug('setDialogPosition:', dialog, dom, pos);
}
public setPinnedDelimiter() {
if(!USEPINNEDDELIMITER) return;
let index = -1;
let dialogs = appMessagesManager.dialogsStorage[0];
let dialogs = appMessagesManager.dialogsStorage.getFolder(0);
for(let dialog of dialogs) {
if(dialog.pFlags?.pinned) {
index++;
@ -731,7 +940,7 @@ export class AppDialogsManager { @@ -731,7 +940,7 @@ export class AppDialogsManager {
dom.lastTimeSpan.innerHTML = timeStr;
} else dom.lastTimeSpan.innerHTML = '';
if((this.doms[peerID] || this.domsArchived[peerID]) == dom) {
if(this.doms[peerID] == dom) {
this.setUnreadMessages(dialog);
} else { // means search
dom.listEl.dataset.mid = lastMessage.mid;
@ -739,18 +948,22 @@ export class AppDialogsManager { @@ -739,18 +948,22 @@ export class AppDialogsManager {
}
public setUnreadMessages(dialog: Dialog) {
let dom = this.getDialogDom(dialog.peerID);
const dom = this.getDialogDom(dialog.peerID);
if(dialog.folder_id == 1) {
this.accumulateArchivedUnread();
}
if(!dom) {
this.log.error('setUnreadMessages no dom!', dialog);
return;
}
let lastMessage = appMessagesManager.getMessage(dialog.top_message);
const lastMessage = appMessagesManager.getMessage(dialog.top_message);
if(lastMessage._ != 'messageEmpty' && !lastMessage.deleted &&
lastMessage.from_id == $rootScope.myID && lastMessage.peerID != $rootScope.myID &&
dialog.read_outbox_max_id) { // maybe comment, 06.20.2020
let outgoing = (lastMessage.pFlags && lastMessage.pFlags.unread)
const outgoing = (lastMessage.pFlags && lastMessage.pFlags.unread)
/* && dialog.read_outbox_max_id != 0 */; // maybe uncomment, 31.01.2020
//console.log('outgoing', outgoing, lastMessage);
@ -766,34 +979,38 @@ export class AppDialogsManager { @@ -766,34 +979,38 @@ export class AppDialogsManager {
dom.unreadMessagesSpan.innerText = '';
dom.unreadMessagesSpan.classList.remove('tgico-pinnedchat');
const filter = appMessagesManager.filtersStorage.filters[this.filterID];
let isPinned: boolean;
if(filter) {
isPinned = filter.pinned_peers.findIndex(peerID => peerID == dialog.peerID) !== -1;
} else {
isPinned = !!dialog.pFlags.pinned;
}
if(dialog.unread_count || dialog.pFlags.unread_mark) {
//dom.unreadMessagesSpan.innerText = '' + (dialog.unread_count ? formatNumber(dialog.unread_count, 1) : ' ');
dom.unreadMessagesSpan.innerText = '' + (dialog.unread_count || ' ');
//dom.unreadMessagesSpan.classList.remove('tgico-pinnedchat');
dom.unreadMessagesSpan.classList.add(new Date(dialog.notify_settings?.mute_until * 1000) > new Date() ?
dom.unreadMessagesSpan.classList.add((dialog.notify_settings?.mute_until * 1000) > Date.now() ?
'unread-muted' : 'unread');
} else if(dialog.pFlags.pinned && dialog.folder_id == 0) {
} else if(isPinned) {
dom.unreadMessagesSpan.classList.remove('unread', 'unread-muted');
dom.unreadMessagesSpan.classList.add('tgico-pinnedchat');
}
}
// set archived new count
if(dialog.folder_id == 1) {
let sum = Object.keys(this.domsArchived).map(p => +p).reduce((acc: number, peerID: number) => {
let dialog = appMessagesManager.getDialogByPeerID(peerID)[0];
if(dialog) {
return acc + dialog.unread_count;
}
return acc;
}, 0);
public accumulateArchivedUnread() {
if(this.accumulateArchivedTimeout) return;
this.accumulateArchivedTimeout = setTimeout(() => {
this.accumulateArchivedTimeout = 0;
const dialogs = appMessagesManager.dialogsStorage.getFolder(1);
const sum = dialogs.reduce((acc, dialog) => acc + dialog.unread_count, 0);
$rootScope.$broadcast('dialogs_archived_unread', {count: sum});
}
}, 0);
}
public getDialogDom(peerID: number) {
return this.doms[peerID] || this.domsArchived[peerID];
return this.doms[peerID];
}
public addDialog(_dialog: Dialog | number, container?: HTMLUListElement | Scrollable, drawStatus = true, rippleEnabled = true, onlyFirstName = false, meAsSaved = true) {
@ -815,7 +1032,14 @@ export class AppDialogsManager { @@ -815,7 +1032,14 @@ export class AppDialogsManager {
let peerID: number = dialog.peerID;
if((this.doms[peerID] || this.domsArchived[peerID]) && !container) return;
if(!container) {
if(this.doms[peerID]) return;
const filter = appMessagesManager.filtersStorage.filters[this.filterID];
if((filter && !appMessagesManager.filtersStorage.testDialogForFilter(dialog, filter)) || (!filter && this.filterID != dialog.folder_id)) {
return;
}
}
let title = appPeersManager.getPeerTitle(peerID, false, onlyFirstName);
@ -848,15 +1072,18 @@ export class AppDialogsManager { @@ -848,15 +1072,18 @@ export class AppDialogsManager {
let titleSpan = document.createElement('span');
titleSpan.classList.add('user-title');
if(peerID < 0) {
let chat = appChatsManager.getChat(-peerID);
if(chat && chat.pFlags && chat.pFlags.verified) {
titleSpan.classList.add('is-verified');
}
} else {
let user = appUsersManager.getUser(peerID);
if(user && user.pFlags && user.pFlags.verified) {
titleSpan.classList.add('is-verified');
// в других случаях иконка верификации не нужна (а первый - это главные чатлисты)
if(!container) {
if(peerID < 0) {
let chat = appChatsManager.getChat(-peerID);
if(chat && chat.pFlags && chat.pFlags.verified) {
titleSpan.classList.add('is-verified');
}
} else {
let user = appUsersManager.getUser(peerID);
if(user && user.pFlags && user.pFlags.verified) {
titleSpan.classList.add('is-verified');
}
}
}
@ -932,14 +1159,20 @@ export class AppDialogsManager { @@ -932,14 +1159,20 @@ export class AppDialogsManager {
listEl: li
};
if(!container) {
if(dialog.folder_id && dialog.folder_id == 1) {
this.scrollArchived.append(li);
this.domsArchived[dialog.peerID] = dom;
} else {
this.scroll.append(li);
this.doms[dialog.peerID] = dom;
/* let good = false;
for(const folderID in this.chatLists) {
if(this.chatLists[folderID] == container) {
good = true;
}
} */
if(!container/* || good */) {
this.scroll.append(li);
this.doms[dialog.peerID] = dom;
/* if(container) {
container.append(li);
} */
this.setLastMessage(dialog);
} else {
@ -950,7 +1183,10 @@ export class AppDialogsManager { @@ -950,7 +1183,10 @@ export class AppDialogsManager {
}
public setTyping(dialog: Dialog, user: User) {
let dom = this.getDialogDom(dialog.peerID);
const dom = this.getDialogDom(dialog.peerID);
if(!dom) {
return;
}
let str = '';
if(dialog.peerID < 0) {
@ -959,7 +1195,7 @@ export class AppDialogsManager { @@ -959,7 +1195,7 @@ export class AppDialogsManager {
str = s + ' ';
}
let senderBold = document.createElement('i');
const senderBold = document.createElement('i');
str += 'typing...';
senderBold.innerHTML = str;
@ -969,10 +1205,15 @@ export class AppDialogsManager { @@ -969,10 +1205,15 @@ export class AppDialogsManager {
}
public unsetTyping(dialog: Dialog) {
let dom = this.getDialogDom(dialog.peerID);
const dom = this.getDialogDom(dialog.peerID);
if(!dom) {
return;
}
dom.lastMessageSpan.classList.remove('user-typing');
this.setLastMessage(dialog, null, dom);
}
}
export default new AppDialogsManager();
const appDialogsManager = new AppDialogsManager();
export default appDialogsManager;

916
src/lib/appManagers/appMessagesManager.ts

File diff suppressed because it is too large Load Diff

7
src/lib/appManagers/appPeersManager.ts

@ -97,13 +97,14 @@ const AppPeersManager = { @@ -97,13 +97,14 @@ const AppPeersManager = {
},
getPeerID: (peerString: any): number => {
if(isObject(peerString)) {
if(typeof(peerString) === 'number') return peerString;
else if(isObject(peerString)) {
return peerString.user_id
? peerString.user_id
: -(peerString.channel_id || peerString.chat_id);
} else if(!peerString) return 0;
let isUser = peerString.charAt(0) == 'u';
let peerParams = peerString.substr(1).split('_');
const isUser = peerString.charAt(0) == 'u';
const peerParams = peerString.substr(1).split('_');
return isUser ? peerParams[0] : -peerParams[0] || 0;
},

28
src/lib/appManagers/appSidebarLeft.ts

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
//import { logger } from "../polyfill";
import appDialogsManager from "./appDialogsManager";
import appDialogsManager, { AppArchivedTab, archivedTab } from "./appDialogsManager";
import { $rootScope } from "../utils";
import appImManager from "./appImManager";
import AppSearch, { SearchGroup } from "../../components/appSearch";
@ -14,6 +14,9 @@ import AppContactsTab from "../../components/sidebarLeft/contacts"; @@ -14,6 +14,9 @@ import AppContactsTab from "../../components/sidebarLeft/contacts";
import AppNewGroupTab from "../../components/sidebarLeft/newGroup";
import AppSettingsTab from "../../components/sidebarLeft/settings";
import AppEditProfileTab from "../../components/sidebarLeft/editProfile";
import AppChatFoldersTab from "../../components/sidebarLeft/chatFolders";
import AppEditFolderTab from "../../components/sidebarLeft/editFolder";
import AppIncludedChatsTab from "../../components/sidebarLeft/includedChats";
import SidebarSlider from "../../components/slider";
import SearchInput from "../../components/searchInput";
@ -25,6 +28,9 @@ const contactsTab = new AppContactsTab(); @@ -25,6 +28,9 @@ const contactsTab = new AppContactsTab();
const newGroupTab = new AppNewGroupTab();
const settingsTab = new AppSettingsTab();
const editProfileTab = new AppEditProfileTab();
const chatFoldersTab = new AppChatFoldersTab();
const editFolderTab = new AppEditFolderTab();
const includedChatsTab = new AppIncludedChatsTab();
export class AppSidebarLeft extends SidebarSlider {
public static SLIDERITEMSIDS = {
@ -35,6 +41,9 @@ export class AppSidebarLeft extends SidebarSlider { @@ -35,6 +41,9 @@ export class AppSidebarLeft extends SidebarSlider {
newGroup: 5,
settings: 6,
editProfile: 7,
chatFolders: 8,
editFolder: 9,
includedChats: 10,
};
private toolsBtn: HTMLButtonElement;
@ -61,17 +70,21 @@ export class AppSidebarLeft extends SidebarSlider { @@ -61,17 +70,21 @@ export class AppSidebarLeft extends SidebarSlider {
privateChat: HTMLButtonElement,
} = {} as any;
public archivedTab: AppArchivedTab;
public newChannelTab: AppNewChannelTab;
public addMembersTab: AppAddMembersTab;
public contactsTab: AppContactsTab;
public newGroupTab: AppNewGroupTab;
public settingsTab: AppSettingsTab;
public editProfileTab: AppEditProfileTab;
public chatFoldersTab: AppChatFoldersTab;
public editFolderTab: AppEditFolderTab;
public includedChatsTab: AppIncludedChatsTab;
//private log = logger('SL');
private searchGroups = {
contacts: new SearchGroup('Contacts and Chats', 'contacts'),
contacts: new SearchGroup('Chats', 'contacts'),
globalContacts: new SearchGroup('Global Search', 'contacts'),
messages: new SearchGroup('Global Search', 'messages'),
people: new SearchGroup('People', 'contacts', false, 'search-group-people'),
@ -81,13 +94,16 @@ export class AppSidebarLeft extends SidebarSlider { @@ -81,13 +94,16 @@ export class AppSidebarLeft extends SidebarSlider {
constructor() {
super(document.getElementById('column-left') as HTMLDivElement, {
//[AppSidebarLeft.SLIDERITEMSIDS.archived]: ,
[AppSidebarLeft.SLIDERITEMSIDS.archived]: archivedTab,
[AppSidebarLeft.SLIDERITEMSIDS.newChannel]: newChannelTab,
[AppSidebarLeft.SLIDERITEMSIDS.contacts]: contactsTab,
[AppSidebarLeft.SLIDERITEMSIDS.addMembers]: addMembersTab,
[AppSidebarLeft.SLIDERITEMSIDS.newGroup]: newGroupTab,
[AppSidebarLeft.SLIDERITEMSIDS.settings]: settingsTab,
[AppSidebarLeft.SLIDERITEMSIDS.editProfile]: editProfileTab,
[AppSidebarLeft.SLIDERITEMSIDS.chatFolders]: chatFoldersTab,
[AppSidebarLeft.SLIDERITEMSIDS.editFolder]: editFolderTab,
[AppSidebarLeft.SLIDERITEMSIDS.includedChats]: includedChatsTab,
});
this.searchInput = new SearchInput('Telegram Search');
@ -97,12 +113,16 @@ export class AppSidebarLeft extends SidebarSlider { @@ -97,12 +113,16 @@ export class AppSidebarLeft extends SidebarSlider {
this.backBtn = this.sidebarEl.querySelector('.sidebar-back-button') as HTMLButtonElement;
this.searchContainer = this.sidebarEl.querySelector('#search-container') as HTMLDivElement;
this.archivedTab = archivedTab;
this.newChannelTab = newChannelTab;
this.addMembersTab = addMembersTab;
this.contactsTab = contactsTab;
this.newGroupTab = newGroupTab;
this.settingsTab = settingsTab;
this.editProfileTab = editProfileTab;
this.chatFoldersTab = chatFoldersTab;
this.editFolderTab = editFolderTab;
this.includedChatsTab = includedChatsTab;
this.menuEl = this.toolsBtn.querySelector('.btn-menu');
this.newBtnMenu = this.sidebarEl.querySelector('#new-menu');
@ -157,7 +177,7 @@ export class AppSidebarLeft extends SidebarSlider { @@ -157,7 +177,7 @@ export class AppSidebarLeft extends SidebarSlider {
});
this.backBtn.addEventListener('click', (e) => {
appDialogsManager.chatsArchivedContainer.classList.remove('active');
//appDialogsManager.chatsArchivedContainer.classList.remove('active');
this.toolsBtn.classList.add('active');
this.backBtn.classList.remove('active');
this.searchContainer.classList.remove('active');

2
src/lib/appManagers/appSidebarRight.ts

@ -368,7 +368,7 @@ class AppPollResultsTab implements SliderTab { @@ -368,7 +368,7 @@ class AppPollResultsTab implements SliderTab {
if(left <= 0) return;
const showMore = document.createElement('div');
showMore.classList.add('poll-results-more');
showMore.classList.add('poll-results-more', 'show-more');
showMore.addEventListener('click', load);
showMore.innerHTML = `<div class="tgico-down"></div><div>Show ${Math.min(20, left)} more voter${left > 1 ? 's' : ''}</div>`;

41
src/lib/appManagers/appUsersManager.ts

@ -41,8 +41,8 @@ export class AppUsersManager { @@ -41,8 +41,8 @@ export class AppUsersManager {
public userAccess: {[userID: number]: string} = {};
public cachedPhotoLocations: any = {};
public contactsIndex = searchIndexManager.createIndex();
public contactsFillPromise: Promise<number[]>;
public contactsList: number[];
public contactsFillPromise: Promise<Set<number>>;
public contactsList: Set<number> = new Set();
public myID: number;
constructor() {
@ -119,20 +119,21 @@ export class AppUsersManager { @@ -119,20 +119,21 @@ export class AppUsersManager {
return this.contactsFillPromise = apiManager.invokeApi('contacts.getContacts', {
hash: 0
}).then((result: any) => {
let userID: number;
this.contactsList = [];
this.saveApiUsers(result.users);
result.contacts.forEach((contact: any) => {
userID = contact.user_id;
this.contactsList.push(userID);
searchIndexManager.indexObject(userID, this.getUserSearchText(userID), this.contactsIndex);
this.pushContact(contact.user_id);
});
return this.contactsList;
});
}
public pushContact(userID: number) {
this.contactsList.add(userID);
searchIndexManager.indexObject(userID, this.getUserSearchText(userID), this.contactsIndex);
}
public getUserSearchText(id: number) {
const user = this.users[id];
if(!user) {
@ -148,10 +149,11 @@ export class AppUsersManager { @@ -148,10 +149,11 @@ export class AppUsersManager {
}
public getContacts(query?: string) {
return this.fillContacts().then(contactsList => {
return this.fillContacts().then(_contactsList => {
let contactsList = [..._contactsList];
if(query) {
const results: any = searchIndexManager.search(query, this.contactsIndex);
const filteredContactsList = contactsList.filter(id => !!results[id]);
const filteredContactsList = [...contactsList].filter(id => !!results[id]);
contactsList = filteredContactsList;
}
@ -300,13 +302,13 @@ export class AppUsersManager { @@ -300,13 +302,13 @@ export class AppUsersManager {
return 'bot';
}
let user = this.getUser(userID);
if(!user || !user.status) {
const user = this.getUser(userID);
if(!user) {
return '';
}
let str = '';
switch(user.status._) {
switch(user.status?._) {
case 'userStatusRecently': {
str = 'last seen recently';
break;
@ -325,19 +327,19 @@ export class AppUsersManager { @@ -325,19 +327,19 @@ export class AppUsersManager {
case 'userStatusOffline': {
str = 'last seen ';
let date = user.status.was_online;
let now = Date.now() / 1000;
const date = user.status.was_online;
const now = Date.now() / 1000;
if((now - date) < 60) {
str += ' just now';
} else if((now - date) < 3600) {
let c = (now - date) / 60 | 0;
const c = (now - date) / 60 | 0;
str += c + ' ' + (c == 1 ? 'minute' : 'minutes') + ' ago';
} else if(now - date < 86400) {
let c = (now - date) / 3600 | 0;
const c = (now - date) / 3600 | 0;
str += c + ' ' + (c == 1 ? 'hour' : 'hours') + ' ago';
} else {
let d = new Date(date * 1000);
const 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);
}
@ -349,6 +351,11 @@ export class AppUsersManager { @@ -349,6 +351,11 @@ export class AppUsersManager {
str = 'online';
break;
}
default: {
str = 'last seen a long time ago';
break;
}
}
return str;

15
src/lib/lottieLoader.ts

@ -1,6 +1,7 @@ @@ -1,6 +1,7 @@
import { isApple, mediaSizes, isSafari } from "./config";
import { logger, LogLevels } from "./polyfill";
import animationIntersector from "../components/animationIntersector";
import apiManager from "./mtproto/mtprotoworker";
let convert = (value: number) => {
return Math.round(Math.min(Math.max(value, 0), 1) * 255);
@ -603,6 +604,19 @@ class LottieLoader { @@ -603,6 +604,19 @@ class LottieLoader {
}
}
public loadAnimationFromURL(params: Omit<RLottieOptions, 'animationData'>, url: string) {
if(!this.loaded) {
this.loadLottieWorkers();
}
return fetch(url)
.then(res => res.arrayBuffer())
.then(data => apiManager.gzipUncompress<string>(data, true))
.then(str => {
return this.loadAnimationWorker(Object.assign(params, {animationData: JSON.parse(str)}));
});
}
public async loadAnimationWorker(params: RLottieOptions, group = '', toneIndex = -1) {
//params.autoplay = true;
@ -638,6 +652,7 @@ class LottieLoader { @@ -638,6 +652,7 @@ class LottieLoader {
return;
}
this.log.debug('onPlayerLoaded');
rlPlayer.onLoad(frameCount, fps);
//rlPlayer.addListener('firstFrame', () => {
//animationIntersector.addAnimation(player, group);

2
src/lib/mtproto/schema.ts

File diff suppressed because one or more lines are too long

11
src/lib/utils.js

@ -297,6 +297,17 @@ export function findUpTag(el, tag) { @@ -297,6 +297,17 @@ export function findUpTag(el, tag) {
return null;
}
export function findUpAttribute(el, attribute) {
if(el.getAttribute(attribute) != null) return el; // 03.02.2020
while(el.parentElement) {
el = el.parentElement;
if(el.getAttribute(attribute) != null)
return el;
}
return null;
}
export function whichChild(elem/* : Node */) {
let i = 0;
// @ts-ignore

22
src/pages/pageAuthCode.ts

@ -236,35 +236,23 @@ let onFirstMount = (): Promise<any> => { @@ -236,35 +236,23 @@ let onFirstMount = (): Promise<any> => {
let imageDiv = page.pageEl.querySelector('.auth-image') as HTMLDivElement;
return Promise.all([
LottieLoader.loadLottieWorkers(),
fetch('assets/img/TwoFactorSetupMonkeyIdle.tgs')
.then(res => res.arrayBuffer())
.then(data => apiManager.gzipUncompress<string>(data, true))
.then(str => LottieLoader.loadAnimationWorker({
LottieLoader.loadAnimationFromURL({
container: imageDiv,
loop: true,
autoplay: true,
animationData: JSON.parse(str),
width: 166,
height: 166
}))
.then(animation => {
}, 'assets/img/TwoFactorSetupMonkeyIdle.tgs').then(animation => {
idleAnimation = animation;
}),
/* false && */fetch('assets/img/TwoFactorSetupMonkeyTracking.tgs')
.then(res => res.arrayBuffer())
.then(data => apiManager.gzipUncompress<string>(data, true))
.then(str => LottieLoader.loadAnimationWorker({
LottieLoader.loadAnimationFromURL({
container: imageDiv,
loop: false,
autoplay: false,
animationData: JSON.parse(str),
width: 166,
height: 166
}))
.then(_animation => {
}, 'assets/img/TwoFactorSetupMonkeyTracking.tgs').then(_animation => {
animation = _animation;
if(!codeInput.value.length) {
@ -272,7 +260,7 @@ let onFirstMount = (): Promise<any> => { @@ -272,7 +260,7 @@ let onFirstMount = (): Promise<any> => {
}
animation.addListener('enterFrame', currentFrame => {
console.log('enterFrame', currentFrame, needFrame);
//console.log('enterFrame', currentFrame, needFrame);
//let currentFrame = Math.round(e.currentTime);
if((animation.direction == 1 && currentFrame >= needFrame) ||

11
src/pages/pagePassword.ts

@ -91,20 +91,13 @@ let onFirstMount = (): Promise<any> => { @@ -91,20 +91,13 @@ let onFirstMount = (): Promise<any> => {
}); */
return Promise.all([
LottieLoader.loadLottieWorkers(),
fetch('assets/img/TwoFactorSetupMonkeyClose.tgs')
.then(res => res.arrayBuffer())
.then(data => apiManager.gzipUncompress<string>(data, true))
.then(str => LottieLoader.loadAnimationWorker({
LottieLoader.loadAnimationFromURL({
container: page.pageEl.querySelector('.auth-image'),
loop: false,
autoplay: false,
animationData: JSON.parse(str),
width: 166,
height: 166
}))
.then(_animation => {
}, 'assets/img/TwoFactorSetupMonkeyClose.tgs').then(_animation => {
animation = _animation;
animation.addListener('enterFrame', currentFrame => {
//console.log('enterFrame', e, needFrame);

72
src/scss/partials/_chat.scss

@ -3,13 +3,17 @@ $time-background: rgba(0, 0, 0, .35); @@ -3,13 +3,17 @@ $time-background: rgba(0, 0, 0, .35);
#bubble-contextmenu > div {
padding: 0 84px 0 16px;
@include respond-to(handhelds) {
padding: 0 60px 0 16px;
}
}
#topbar {
width: 100%;
background-color: #fff;
user-select: none;
box-shadow: 0px 1px 5px -1px rgba(0,0,0,0.18);
box-shadow: 0px 1px 5px -1px rgba(0,0,0,0.21);
z-index: 1;
min-height: 56px;
max-height: 56px;
@ -62,6 +66,10 @@ $time-background: rgba(0, 0, 0, .35); @@ -62,6 +66,10 @@ $time-background: rgba(0, 0, 0, .35);
.btn-menu {
top: calc(100% + 7px);
}
@include respond-to(handhelds) {
margin-left: 0;
}
}
.chat-info {
@ -73,7 +81,11 @@ $time-background: rgba(0, 0, 0, .35); @@ -73,7 +81,11 @@ $time-background: rgba(0, 0, 0, .35);
// padding-left: 17px;
// line-height: 1.6;
padding-left: 10px;
line-height: 1.3;
//line-height: 1.3;
@include respond-to(handhelds) {
max-width: 208px;
}
}
.person {
@ -88,7 +100,7 @@ $time-background: rgba(0, 0, 0, .35); @@ -88,7 +100,7 @@ $time-background: rgba(0, 0, 0, .35);
.bottom {
font-size: 14px;
line-height: 18px;
//line-height: 18px;
color: #707579;
.online {
@ -127,7 +139,7 @@ $time-background: rgba(0, 0, 0, .35); @@ -127,7 +139,7 @@ $time-background: rgba(0, 0, 0, .35);
@include respond-to(handhelds) {
padding: .35rem .5rem .5rem;
padding: 0 .5rem .5rem;
}
@include respond-to(not-handhelds) {
@ -214,7 +226,7 @@ $time-background: rgba(0, 0, 0, .35); @@ -214,7 +226,7 @@ $time-background: rgba(0, 0, 0, .35);
height: 10px;
background-color: #e53935;
border-radius: 50%;
margin: 0 .5rem;
margin: 0 9px;
display: inline-block;
animation: recordBlink 1.25s infinite;
}
@ -231,13 +243,20 @@ $time-background: rgba(0, 0, 0, .35); @@ -231,13 +243,20 @@ $time-background: rgba(0, 0, 0, .35);
left: -48px;
transition: transform .03s, visibility .1s;
visibility: hidden;
@include respond-to(handhelds) {
width: 300px;
height: 300px;
top: -124px;
left: -124px;
}
}
&.is-recording {
#btn-record-cancel {
opacity: 1;
visibility: visible;
margin-right: .5rem;
margin-right: 9px;
transition: width .1s, margin-right .1s, visibility 0s .1s, opacity .1s .1s;
}
@ -279,14 +298,24 @@ $time-background: rgba(0, 0, 0, .35); @@ -279,14 +298,24 @@ $time-background: rgba(0, 0, 0, .35);
#im-title {
cursor: pointer;
margin-top: -2px;
font-size: 18px;
line-height: 24px;
@include respond-to(handhelds) {
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
}
span.emoji {
vertical-align: inherit;
}
}
.info#im-subtitle {
margin-top: -2px;
}
.chat-container {
display: flex;
// padding: 200px;
@ -471,6 +500,14 @@ $time-background: rgba(0, 0, 0, .35); @@ -471,6 +500,14 @@ $time-background: rgba(0, 0, 0, .35);
// height: 100%;
height: 52px;
padding: 1rem;
&-subtitle {
line-height: 13px !important;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
max-width: 280px;
}
}
}
@ -530,9 +567,9 @@ $time-background: rgba(0, 0, 0, .35); @@ -530,9 +567,9 @@ $time-background: rgba(0, 0, 0, .35);
white-space: nowrap;
text-overflow: ellipsis;
@include respond-to(handhelds) {
line-height: 13px;
}
// @include respond-to(handhelds) {
// line-height: 13px;
// }
}
&-subtitle {
@ -765,6 +802,9 @@ $time-background: rgba(0, 0, 0, .35); @@ -765,6 +802,9 @@ $time-background: rgba(0, 0, 0, .35);
//flex-direction: unset;
display: block;
@include respond-to(handhelds) {
padding: 0 .5rem;
}
/* display: flex;
flex-direction: column;
@ -814,10 +854,12 @@ $time-background: rgba(0, 0, 0, .35); @@ -814,10 +854,12 @@ $time-background: rgba(0, 0, 0, .35);
&.is-chat {
.is-in .bubble__container {
margin-left: 3rem;
margin-left: 45px;
//margin-left: 3rem; #DO JS3
@include respond-to(handhelds) {
max-width: calc(100% - 3rem);
margin-left: 45px;
}
}
}
@ -899,7 +941,7 @@ $time-background: rgba(0, 0, 0, .35); @@ -899,7 +941,7 @@ $time-background: rgba(0, 0, 0, .35);
display: flex;
justify-content: space-between;
align-items: center;
box-shadow: 0px -1px 5px -1px rgba(0,0,0,0.18);
box-shadow: 0px -1px 5px -1px rgba(0,0,0,0.21);
.chat-search-count {
margin-left: 8px;
@ -956,7 +998,7 @@ $time-background: rgba(0, 0, 0, .35); @@ -956,7 +998,7 @@ $time-background: rgba(0, 0, 0, .35);
width: 100%;
justify-content: center;
z-index: 5;
top: 10px;
top: 8px;
align-items: center;
transform: translateY(calc(-100% - 10px));
transition: transform .2s ease;
@ -969,7 +1011,7 @@ $time-background: rgba(0, 0, 0, .35); @@ -969,7 +1011,7 @@ $time-background: rgba(0, 0, 0, .35);
background: rgba(0, 0, 0, .7);
text-align: center;
width: auto;
padding: 12px 18px 12px 48px;
padding: 10px 18px 12px 50px;
min-height: 48px;
border-radius: 12px;
line-height: 1.5;
@ -990,7 +1032,7 @@ $time-background: rgba(0, 0, 0, .35); @@ -990,7 +1032,7 @@ $time-background: rgba(0, 0, 0, .35);
&:before {
content: $tgico-info2;
position: absolute;
left: 12px;
left: 15px;
font-size: 1.5rem;
top: 12px;
}

86
src/scss/partials/_chatBubble.scss

@ -49,6 +49,10 @@ $bubble-margin: .25rem; @@ -49,6 +49,10 @@ $bubble-margin: .25rem;
margin: 0 auto;
}
@include respond-to(handhelds) {
max-width: unquote("min(calc(100% - 46px), #{$chat-max-width})");
}
&.is-selected {
&:after {
position: absolute;
@ -67,6 +71,10 @@ $bubble-margin: .25rem; @@ -67,6 +71,10 @@ $bubble-margin: .25rem;
}
&.is-first-unread {
@include respond-to(handhelds) {
max-width: unset;
}
&:before {
content: "Unread messages";
height: 30px;
@ -135,13 +143,18 @@ $bubble-margin: .25rem; @@ -135,13 +143,18 @@ $bubble-margin: .25rem;
> .user-avatar {
position: absolute;
left: -3rem;
left: -45px;
//left: -3rem; # DO JS3
width: 40px;
height: 40px;
line-height: 40px;
bottom: 0;
font-size: 1rem;
cursor: pointer;
// @include respond-to(handhelds) {
// left: -45px;
// }
}
}
@ -630,6 +643,11 @@ $bubble-margin: .25rem; @@ -630,6 +643,11 @@ $bubble-margin: .25rem;
top: 0;
margin-bottom: 0;
@include respond-to(handhelds) {
padding: 8px 6px 8px 8px;
max-width: 94px;
}
.reply-content {
margin-top: 0;
}
@ -1088,6 +1106,10 @@ $bubble-margin: .25rem; @@ -1088,6 +1106,10 @@ $bubble-margin: .25rem;
.reply {
left: calc(100% + 10px);
background-color: #fff;
@include respond-to(handhelds) {
left: calc(100% + 1px);
}
}
}
}
@ -1443,7 +1465,8 @@ $bubble-margin: .25rem; @@ -1443,7 +1465,8 @@ $bubble-margin: .25rem;
poll-element {
margin-top: -1px;
display: block;
min-width: 280px;
//min-width: 280px;
min-width: 330px;
&:not(.is-closed):not(.is-voted) .poll-answer {
cursor: pointer;
@ -1458,6 +1481,7 @@ poll-element { @@ -1458,6 +1481,7 @@ poll-element {
&-desc {
font-size: 14px;
color: #707579;
margin-top: 2px;
margin-bottom: 7px;
user-select: none;
display: flex;
@ -1466,8 +1490,9 @@ poll-element { @@ -1466,8 +1490,9 @@ poll-element {
&-hint {
position: absolute;
right: 0;
font-size: 1.5rem;
top: -4px;
right: 2px;
color: #50a2e9;
cursor: pointer;
transform: scale(1);
@ -1485,18 +1510,19 @@ poll-element { @@ -1485,18 +1510,19 @@ poll-element {
&-avatars {
display: flex;
margin-left: 1rem;
margin-left: 18px;
}
&-answer {
display: flex;
position: relative;
padding-bottom: 20px;
padding-left: 34px;
padding-left: 28px;
margin-top: 1px;
&-text {
margin-top: 7px;
margin-left: 14px;
margin-top: 6px;
margin-left: 12px;
user-select: none;
}
@ -1507,8 +1533,9 @@ poll-element { @@ -1507,8 +1533,9 @@ poll-element {
opacity: 0;
font-weight: 500;
margin-top: 7px;
font-size: 14px;
transition: .34s opacity;
margin-left: -3px;
margin-left: -9px;
text-align: right;
width: 40px;
user-select: none;
@ -1517,7 +1544,7 @@ poll-element { @@ -1517,7 +1544,7 @@ poll-element {
&-selected {
position: absolute;
bottom: 1px;
left: 22px;
left: 15px;
color: #fff;
background: #50a2e9;
border-radius: 50%;
@ -1577,12 +1604,13 @@ poll-element { @@ -1577,12 +1604,13 @@ poll-element {
color: #707579;
font-size: 14px;
user-select: none;
padding-top: 1px;
}
&-line {
height: 35px;
position: absolute;
left: 17.5px;
left: 10px;
bottom: 2px;
transition: stroke-dashoffset .34s linear, stroke-dasharray .34s linear;
stroke-dashoffset: 0;
@ -1623,12 +1651,23 @@ poll-element { @@ -1623,12 +1651,23 @@ poll-element {
}
&-quiz-timer {
width: 30px;
height: 30px;
stroke: #DF3F40;
width: 32px;
height: 32px;
stroke: #a3adb6;
transform: rotate(270deg);
top: -7px;
fill: none;
position: absolute;
right: 0;
right: -2px;
stroke-linecap: round;
}
&-time {
font-size: 12px;
font-weight: 500;
position: absolute;
right: 27px;
color: #a3adb6;
}
}
@ -1641,7 +1680,7 @@ poll-element { @@ -1641,7 +1680,7 @@ poll-element {
.poll-answer-selected {
background: #DF3F40;
line-height: 16px;
//line-height: 16px;
&:before {
content: $tgico-close;
@ -1660,8 +1699,8 @@ poll-element { @@ -1660,8 +1699,8 @@ poll-element {
}
avatar-element {
width: 20px;
height: 20px;
width: 18px;
height: 18px;
border: 1px solid #fff;
line-height: 20px;
font-size: 10px;
@ -1680,10 +1719,9 @@ poll-element { @@ -1680,10 +1719,9 @@ poll-element {
align-items: center;
width: 34px;
height: 34px;
margin-left: 5px;
position: absolute;
left: 0;
top: 0;
left: -1px;
top: -1px;
transform: scale(1);
transition: .1s transform;
@ -1693,9 +1731,9 @@ poll-element { @@ -1693,9 +1731,9 @@ poll-element {
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
width: 16px;
height: 16px;
font-size: 16px;
width: 20px;
height: 20px;
font-size: 20px;
line-height: 16px;
animation: none;
transition: opacity .2s ease;
@ -1727,7 +1765,7 @@ poll-element { @@ -1727,7 +1765,7 @@ poll-element {
stroke-dashoffset: 0;
stroke-opacity: 1;
stroke-width: 2;
stroke: #8d969c;
stroke: #dadbdc;
fill: transparent;
}
}

13
src/scss/partials/_chatlist.scss

@ -84,7 +84,7 @@ @@ -84,7 +84,7 @@
li {
//padding: 0 0 2px 0;
padding-bottom: 4px;
//padding-bottom: 4px; - DO MAKETA JS3
//overflow: hidden;
background-color: #fff;
@ -106,7 +106,7 @@ @@ -106,7 +106,7 @@
position: relative;
cursor: pointer;
padding: 9px 8.5px;
margin: 0px 8px 0px 7px;
margin: 0px 8px 0px 8px;
overflow: hidden;
@media not all and (min-resolution:.001dpcm)
@ -116,8 +116,9 @@ @@ -116,8 +116,9 @@
}
@include respond-to(handhelds) {
padding: 9px 0 0 0;
margin: 0px 11.5px 0px 9px;
padding: 9px 12px 0 9px;
border-radius: 0;
margin: 0;
overflow: hidden;
}
@ -189,8 +190,8 @@ @@ -189,8 +190,8 @@
overflow: hidden;
color: $color-gray;
flex: 1 1 auto;
padding: 1px 3.5px 1px 9px;
//padding: 1px 3.5px 1px 9px; - DO MAKETA JS3
padding: 1px 8.5px 1px 9px; // JS3
p:last-child {
margin-top: -3px;
}

71
src/scss/partials/_emojiDropdown.scss

@ -11,7 +11,7 @@ @@ -11,7 +11,7 @@
@include respond-to(not-handhelds) {
position: absolute !important;
left: 0;
bottom: calc(82px);
bottom: calc(85px);
width: 420px !important;
height: 420px;
box-shadow: 0px 5px 10px 5px rgba(16, 35, 47, 0.14);
@ -30,6 +30,7 @@ @@ -30,6 +30,7 @@
> .menu-horizontal {
//font-weight: 500;
margin-top: 2px;
border: none;
}
.emoji-container {
@ -51,11 +52,13 @@ @@ -51,11 +52,13 @@
&-search {
position: absolute;
left: 0;
margin-left: 4px !important;
}
&-delete {
position: absolute;
right: 0;
margin-right: 4px !important;
}
}
@ -64,18 +67,21 @@ @@ -64,18 +67,21 @@
height: 100%;
.category-title {
position: sticky;
//position: sticky;
top: 0;
font-size: .85rem;
//font-size: .85rem;
font-size: 14px;
font-weight: 500;
color: $color-gray;
background: linear-gradient(to bottom,#fff 0,rgba(255,255,255,.9) 60%,rgba(255,255,255,0) 100%);
//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;
//padding: .53333rem 6PX .66667rem;
padding: 12px 6px 6px 6px;
width: 100%;
}
.emoji-category {
padding-top: 1px;
//padding-top: 1px;
position: relative;
.category-items {
@ -109,7 +115,7 @@ @@ -109,7 +115,7 @@
}
&:first-child {
padding-top: 5px;
//padding-top: 5px;
}
/* &::after {
@ -188,35 +194,42 @@ @@ -188,35 +194,42 @@
.emoji-padding {
.menu-horizontal {
border-bottom: 1px solid $lightgrey;
//border-bottom: 1px solid $lightgrey;
}
}
.emoji-padding, .stickers-padding {
.menu-horizontal {
height: 47px;
height: 48px;
border-bottom: none;
padding: 2px 2px 2px 2px;
width: 100%;
box-shadow: 0px 1px 5px -1px rgba(0, 0, 0, 0.21);
z-index: 4;
li {
margin: 0;
}
}
}
#content-stickers {
.scrollable {
padding: 15px 5px 0;
padding: 0px 5px 0;
}
}
.menu-horizontal {
width: 100%;
height: 50px;
height: 48px;
box-shadow: 0px -2px 5px -1px rgba(0, 0, 0, 0.21);
li {
font-size: 1.65rem;
padding: 0;
width: 50px;
height: 50px;
line-height: 50px;
font-size: 1.5rem;
margin: 0 12px;
width: 48px;
height: 48px;
line-height: 48px;
display: flex;
align-items: center;
flex: 0 0 auto;
@ -224,11 +237,30 @@ @@ -224,11 +237,30 @@
}
.stickers-padding {
&.active {
.scrollable {
padding: 0;
box-shadow: 0px 1px 5px -1px rgba(0, 0, 0, 0.21);
}
.menu-horizontal {
box-shadow: none;
& li {
height: 48px;
width: 48px;
padding: 0;
margin-right: 1px;
margin-left: 1px;
}
}
}
.menu-wrapper {
padding: 0;
height: 50px;
height: 48px;
max-width: 100%;
border-top: 1px solid $lightgrey;
//border-top: 1px solid $lightgrey;
}
li {
@ -242,7 +274,8 @@ @@ -242,7 +274,8 @@
}
> canvas, > img {
padding: .75rem;
//padding: .75rem;
padding: 8px;
max-width: 100%;
max-height: 100%;
width: 100%;

227
src/scss/partials/_leftSidebar.scss

@ -6,6 +6,24 @@ @@ -6,6 +6,24 @@
max-height: 100%;
overflow: hidden;
position: relative;
.folders-tabs-scrollable {
position: sticky;
top: 0;
z-index: 1;
.scrollable {
position: relative;
}
}
.menu-horizontal {
background: #fff;
ul {
justify-content: space-between
}
}
}
.sidebar-slider {
@ -46,6 +64,12 @@ @@ -46,6 +64,12 @@
}
}
}
.btn-menu {
@include respond-to(handhelds) {
margin-top: -4px;
}
}
}
.sidebar-tools-button .btn-menu {
@ -64,6 +88,11 @@ @@ -64,6 +88,11 @@
justify-self: flex-end;
position: absolute;
right: 16px;
@include respond-to(handhelds) {
font-size: 14px;
font-weight: 600;
}
}
.archived-count:empty {
@ -219,6 +248,13 @@ @@ -219,6 +248,13 @@
border-radius: 0.625rem;
margin: 0px 0.5rem 0px 0.4375rem;
@include respond-to(handhelds) {
padding: 0.75rem 0.625rem;
height: 48px;
margin: 0 0 2px 0;
border-radius: 0;
}
html.no-touch &:hover {
background: rgba(112, 117, 121, 0.08);
cursor: pointer;
@ -240,6 +276,10 @@ @@ -240,6 +276,10 @@
&-buttons {
margin-top: .9375rem;
width: 100%;
@include respond-to(handhelds) {
margin-top: 0.6875rem;
}
}
}
}
@ -266,4 +306,191 @@ @@ -266,4 +306,191 @@
.scroll-wrapper {
width: 100%;
}
}
.chat-folders-container, .edit-folder-container {
user-select: none;
.sticker-container {
width: 86px;
height: 86px;
margin: 1px auto 32px;
flex: 0 0 auto;
}
.caption {
text-align: center;
color: #707579;
font-size: 14px;
line-height: 1.3;
}
.sidebar-left-h2 {
color: #707579;
font-size: 15px;
padding-top: 7px;
padding-bottom: 15px;
font-weight: 500;
}
}
.chat-folders-container {
.btn-primary {
width: 160px;
height: 40px;
align-items: center;
margin: 15px auto 24px;
border-radius: 30px;
padding: 0 12px;
display: flex;
}
.tgico-add:before {
content: "\e903";
font-size: 24px;
margin-right: 6px;
}
.folders-container {
padding: 0 16px;
}
.category {
padding: 7px 0 11px 0;
display: flex;
padding-bottom: 11px;
justify-content: space-between;
cursor: pointer;
position: relative;
p:last-child {
color: #707579;
font-size: 14px;
line-height: 1;
}
.btn-primary {
height: 30px;
font-size: 15px;
width: 52px;
transition: width 0.2s;
margin: 5px 0 0 0;
}
}
}
.edit-folder-container {
.caption {
margin-bottom: 18px;
}
@include respond-to(handhelds) {
.input-wrapper {
width: 328px;
}
.input-field input {
height: 50px;
}
}
.sidebar-left-h2 {
padding: 21px 0 8px 16px;
}
.input-wrapper {
margin-bottom: 10px;
}
}
.folder-list {
li {
padding-bottom: 2px;
.rp {
padding: 8px 3px !important;
height: 48px !important;
}
}
avatar-element {
height: 32px;
width: 32px;
}
.user-caption {
padding: 6px 28px;
}
p span {
font-weight: normal;
}
}
.folder-categories {
width: 100%;
}
.folder-category-button {
display: flex;
font-size: 1.5rem;
padding: 13px 16px 10px 16px;
p {
user-select: none;
margin-left: 32px;
font-size: 16px;
}
&.blue, &.blue:before {
color: #50a2e9;
}
&:first-child {
cursor: pointer;
}
&:before {
color: #797d82;
}
}
.sidebar-header .tgico-check1 {
color: #50a2e9;
}
.included-chats-container {
.sidebar-left-h2 {
color: #707579;
font-size: 15px;
font-weight: 500;
padding: 6px 0 8px 16px;
}
ul {
li > .rp {
margin: 0 !important;
padding: 7px 12px !important;
height: 62px;
}
.dialog-avatar {
width: 46px;
height: 46px;
}
span.user-title {
font-weight: 500;
}
.user-caption {
padding: 0px 0px 0 14px;
margin-top: -2px;
}
span.user-last-message {
font-size: 15px;
margin-top: -1px;
}
}
}

6
src/scss/partials/_rightSidebar.scss

@ -120,6 +120,10 @@ @@ -120,6 +120,10 @@
margin-bottom: 2px;
margin-top: 1px;
@include respond-to(handhelds) {
margin-top: 3px;
}
&.online {
color: $color-blue;
}
@ -639,7 +643,7 @@ @@ -639,7 +643,7 @@
height: 48px;
@include respond-to(not-handhelds) {
padding: 8px 13px;
padding: 8px 12px;
}
}
}

4
src/scss/partials/_selector.scss

@ -134,6 +134,10 @@ @@ -134,6 +134,10 @@
margin: 0px 9px 0px 8px;
padding: 12px 8.5px;
@include respond-to(handhelds) {
margin: 0;
}
@media not all and (min-resolution:.001dpcm)
{ @supports (-webkit-appearance:none) {
margin-right: 4px;

6
src/scss/partials/_sidebar.scss

@ -25,8 +25,12 @@ @@ -25,8 +25,12 @@
&__title {
flex: 1;
font-weight: 500;
padding-left: 23px;
padding-left: 22px;
font-size: 20px;
@include respond-to(handhelds) {
padding-left: 24px;
}
}
.btn-icon + .btn-icon {

2
src/scss/partials/_slider.scss

@ -12,6 +12,7 @@ @@ -12,6 +12,7 @@
align-items: center;
position: relative;
z-index: 2;
flex-direction: row;
}
li {
@ -24,6 +25,7 @@ @@ -24,6 +25,7 @@
// font-size: 1rem;
font-size: 14px;
font-weight: 500;
position: relative;
&.active {
color: $color-blue;

16
src/scss/partials/popups/_createPoll.scss

@ -4,10 +4,12 @@ @@ -4,10 +4,12 @@
#{$parent} {
&-container {
max-height: 468px;
padding: 0;
}
}
.input-field {
margin-top: 25px;
.btn-icon {
position: absolute;
right: .5rem;
@ -30,4 +32,18 @@ @@ -30,4 +32,18 @@
}
} */
}
.caption {
color: #707579;
font-weight: 500;
padding: 16px 24px 0;
}
.poll-create-questions {
padding: 0px 20px 32.5px;
}
hr {
border-bottom: 1px solid #edeff1;
}
}

9
src/scss/partials/popups/_mediaAttacher.scss

@ -53,6 +53,7 @@ @@ -53,6 +53,7 @@
justify-content: space-between;
align-items: center;
margin-bottom: 9px;
padding: 12px 20px 15px;
.btn-primary {
width: 79px;
@ -125,14 +126,14 @@ @@ -125,14 +126,14 @@
.input-field {
width: 100%;
margin-top: 1rem;
margin-top: 25px;
&::placeholder {
color: #a2acb4;
}
input {
height: 54px;
//height: 54px;
font-size: 1rem;
padding: 0 15px;
border-radius: $border-radius-medium;
@ -147,4 +148,8 @@ @@ -147,4 +148,8 @@
opacity: 0;
}
}
}
.popup-create-poll.popup-new-media .btn-primary {
width: 94px;
}

27
src/scss/style.scss

@ -347,6 +347,11 @@ input, textarea { @@ -347,6 +347,11 @@ input, textarea {
&.danger:before {
color: $color-error;
}
@include respond-to(handhelds) {
padding: 0 30px 0 16px;
height: 50px;
}
}
}
@ -475,6 +480,7 @@ avatar-element { @@ -475,6 +480,7 @@ avatar-element {
.rp {
position: relative;
user-select: none;
}
/**
@ -853,6 +859,7 @@ avatar-element { @@ -853,6 +859,7 @@ avatar-element {
padding: 0 5px;
left: .75rem;
font-size: 0.75rem!important;
color: #666;
opacity: 1;
}
}
@ -1386,6 +1393,26 @@ img.emoji { @@ -1386,6 +1393,26 @@ img.emoji {
transition: opacity .2s ease;
}
.show-more {
padding-top: 13px;
padding-bottom: 13px;
cursor: pointer;
user-select: none;
position: relative;
@include respond-to(not-handhelds) {
padding-left: 8px;
}
.tgico-down {
float: left;
padding-right: 32px;
padding-left: 16px;
font-size: 24px;
color: #707579;
}
}
/* .fade-in-end {
opacity: 1;
transition: opacity .2s ease;

2
webpack.common.js

@ -5,7 +5,7 @@ const MiniCssExtractPlugin = require('mini-css-extract-plugin'); @@ -5,7 +5,7 @@ const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const postcssPresetEnv = require('postcss-preset-env');
const fs = require('fs');
const allowedIPs = ['195.66.140.39', '192.168.31.144', '127.0.0.1', '192.168.31.1', '192.168.31.192'];
const allowedIPs = ['195.66.140.39', '192.168.31.144', '127.0.0.1', '192.168.31.1', '192.168.31.192', '176.100.18.181'];
const devMode = process.env.NODE_ENV !== 'production';
const useLocal = false;

Loading…
Cancel
Save