Browse Source

FIlters changes:

Handle reordering
Handle updateDialogFilters
Ordering in folder settings
Fix contacts list loading after state if it is empty
master
Eduard Kuzmenko 4 years ago
parent
commit
6c36941b41
  1. 10
      src/components/horizontalMenu.ts
  2. 14
      src/components/sidebarLeft/index.ts
  3. 56
      src/components/sidebarLeft/tabs/chatFolders.ts
  4. 3
      src/components/sidebarLeft/tabs/editFolder.ts
  5. 2
      src/components/sidebarLeft/tabs/includedChats.ts
  6. 2
      src/components/slider.ts
  7. 24
      src/components/transition.ts
  8. 89
      src/lib/appManagers/appDialogsManager.ts
  9. 412
      src/lib/appManagers/appMessagesManager.ts
  10. 4
      src/lib/appManagers/appStateManager.ts
  11. 16
      src/lib/appManagers/appUsersManager.ts
  12. 2
      src/lib/mtproto/mtprotoworker.ts
  13. 4
      src/lib/rootScope.ts
  14. 180
      src/lib/storages/dialogs.ts
  15. 262
      src/lib/storages/filters.ts

10
src/components/horizontalMenu.ts

@ -2,8 +2,6 @@ import { findUpTag, whichChild } from "../helpers/dom";
import Transition from "./transition"; import Transition from "./transition";
export function horizontalMenu(tabs: HTMLElement, content: HTMLElement, onClick?: (id: number, tabContent: HTMLDivElement) => void, onTransitionEnd?: () => void, transitionTime = 250) { export function horizontalMenu(tabs: HTMLElement, content: HTMLElement, onClick?: (id: number, tabContent: HTMLDivElement) => void, onTransitionEnd?: () => void, transitionTime = 250) {
let prevId = -1;
const selectTab = Transition(content, tabs || content.dataset.slider == 'tabs' ? 'tabs' : 'navigation', transitionTime, onTransitionEnd); const selectTab = Transition(content, tabs || content.dataset.slider == 'tabs' ? 'tabs' : 'navigation', transitionTime, onTransitionEnd);
if(tabs) { if(tabs) {
@ -34,7 +32,7 @@ export function horizontalMenu(tabs: HTMLElement, content: HTMLElement, onClick?
const tabContent = content.children[id] as HTMLDivElement; const tabContent = content.children[id] as HTMLDivElement;
if(onClick) onClick(id, tabContent); if(onClick) onClick(id, tabContent);
if(target.classList.contains('active') || id == prevId) { if(target.classList.contains('active') || id == selectTab.prevId) {
return false; return false;
} }
@ -42,9 +40,9 @@ export function horizontalMenu(tabs: HTMLElement, content: HTMLElement, onClick?
prev && prev.classList.remove('active'); prev && prev.classList.remove('active');
// stripe from ZINCHUK // stripe from ZINCHUK
if(useStripe && prevId != -1) { if(useStripe && selectTab.prevId != -1) {
const indicator = target.querySelector('i')!; const indicator = target.querySelector('i')!;
const currentIndicator = target.parentElement.children[prevId].querySelector('i')!; const currentIndicator = target.parentElement.children[selectTab.prevId].querySelector('i')!;
currentIndicator.classList.remove('animate'); currentIndicator.classList.remove('animate');
indicator.classList.remove('animate'); indicator.classList.remove('animate');
@ -66,8 +64,6 @@ export function horizontalMenu(tabs: HTMLElement, content: HTMLElement, onClick?
target.classList.add('active'); target.classList.add('active');
selectTab(id); selectTab(id);
prevId = id;
}); });
} }

14
src/components/sidebarLeft/index.ts

@ -10,7 +10,7 @@ import { MOUNT_CLASS_TO } from "../../lib/mtproto/mtproto_config";
import $rootScope from "../../lib/rootScope"; import $rootScope from "../../lib/rootScope";
import { findUpClassName, findUpTag } from "../../helpers/dom"; import { findUpClassName, findUpTag } from "../../helpers/dom";
import AppSearch, { SearchGroup } from "../appSearch"; import AppSearch, { SearchGroup } from "../appSearch";
import AvatarElement from "../avatar"; import "../avatar";
import { parseMenuButtonsTo } from "../misc"; import { parseMenuButtonsTo } from "../misc";
import { ScrollableX } from "../scrollable"; import { ScrollableX } from "../scrollable";
import SearchInput from "../searchInput"; import SearchInput from "../searchInput";
@ -26,8 +26,8 @@ import AppIncludedChatsTab from "./tabs/includedChats";
import AppNewChannelTab from "./tabs/newChannel"; import AppNewChannelTab from "./tabs/newChannel";
import AppNewGroupTab from "./tabs/newGroup"; import AppNewGroupTab from "./tabs/newGroup";
import AppSettingsTab from "./tabs/settings"; import AppSettingsTab from "./tabs/settings";
import appMessagesManager from "../../lib/appManagers/appMessagesManager";
AvatarElement; import apiManagerProxy from "../../lib/mtproto/mtprotoworker";
const newChannelTab = new AppNewChannelTab(); const newChannelTab = new AppNewChannelTab();
const addMembersTab = new AppAddMembersTab(); const addMembersTab = new AppAddMembersTab();
@ -35,7 +35,6 @@ const contactsTab = new AppContactsTab();
const newGroupTab = new AppNewGroupTab(); const newGroupTab = new AppNewGroupTab();
const settingsTab = new AppSettingsTab(); const settingsTab = new AppSettingsTab();
const editProfileTab = new AppEditProfileTab(); const editProfileTab = new AppEditProfileTab();
const chatFoldersTab = new AppChatFoldersTab();
const editFolderTab = new AppEditFolderTab(); const editFolderTab = new AppEditFolderTab();
const includedChatsTab = new AppIncludedChatsTab(); const includedChatsTab = new AppIncludedChatsTab();
const archivedTab = new AppArchivedTab(); const archivedTab = new AppArchivedTab();
@ -124,7 +123,9 @@ export class AppSidebarLeft extends SidebarSlider {
private recentSearchClearBtn: HTMLElement; private recentSearchClearBtn: HTMLElement;
constructor() { constructor() {
super(document.getElementById('column-left') as HTMLDivElement, { super(document.getElementById('column-left') as HTMLDivElement);
Object.assign(this.tabs, {
[AppSidebarLeft.SLIDERITEMSIDS.archived]: archivedTab, [AppSidebarLeft.SLIDERITEMSIDS.archived]: archivedTab,
[AppSidebarLeft.SLIDERITEMSIDS.newChannel]: newChannelTab, [AppSidebarLeft.SLIDERITEMSIDS.newChannel]: newChannelTab,
[AppSidebarLeft.SLIDERITEMSIDS.contacts]: contactsTab, [AppSidebarLeft.SLIDERITEMSIDS.contacts]: contactsTab,
@ -132,7 +133,7 @@ export class AppSidebarLeft extends SidebarSlider {
[AppSidebarLeft.SLIDERITEMSIDS.newGroup]: newGroupTab, [AppSidebarLeft.SLIDERITEMSIDS.newGroup]: newGroupTab,
[AppSidebarLeft.SLIDERITEMSIDS.settings]: settingsTab, [AppSidebarLeft.SLIDERITEMSIDS.settings]: settingsTab,
[AppSidebarLeft.SLIDERITEMSIDS.editProfile]: editProfileTab, [AppSidebarLeft.SLIDERITEMSIDS.editProfile]: editProfileTab,
[AppSidebarLeft.SLIDERITEMSIDS.chatFolders]: chatFoldersTab, [AppSidebarLeft.SLIDERITEMSIDS.chatFolders]: this.chatFoldersTab = new AppChatFoldersTab(appMessagesManager, appPeersManager, this, apiManagerProxy, $rootScope),
[AppSidebarLeft.SLIDERITEMSIDS.editFolder]: editFolderTab, [AppSidebarLeft.SLIDERITEMSIDS.editFolder]: editFolderTab,
[AppSidebarLeft.SLIDERITEMSIDS.includedChats]: includedChatsTab, [AppSidebarLeft.SLIDERITEMSIDS.includedChats]: includedChatsTab,
}); });
@ -153,7 +154,6 @@ export class AppSidebarLeft extends SidebarSlider {
this.newGroupTab = newGroupTab; this.newGroupTab = newGroupTab;
this.settingsTab = settingsTab; this.settingsTab = settingsTab;
this.editProfileTab = editProfileTab; this.editProfileTab = editProfileTab;
this.chatFoldersTab = chatFoldersTab;
this.editFolderTab = editFolderTab; this.editFolderTab = editFolderTab;
this.includedChatsTab = includedChatsTab; this.includedChatsTab = includedChatsTab;

56
src/components/sidebarLeft/tabs/chatFolders.ts

@ -1,15 +1,16 @@
import { SliderTab } from "../../slider"; import { SliderTab } from "../../slider";
import lottieLoader, { RLottiePlayer } from "../../../lib/lottieLoader"; import lottieLoader, { RLottiePlayer } from "../../../lib/lottieLoader";
import apiManager from "../../../lib/mtproto/mtprotoworker";
import appMessagesManager, { MyDialogFilter } from "../../../lib/appManagers/appMessagesManager";
import { RichTextProcessor } from "../../../lib/richtextprocessor"; import { RichTextProcessor } from "../../../lib/richtextprocessor";
import appPeersManager from "../../../lib/appManagers/appPeersManager"; import { cancelEvent, positionElementByIndex } from "../../../helpers/dom";
import { cancelEvent } from "../../../helpers/dom";
import appSidebarLeft from "..";
import { ripple } from "../../ripple"; import { ripple } from "../../ripple";
import { toast } from "../../toast"; import { toast } from "../../toast";
import { DialogFilterSuggested, DialogFilter } from "../../../layer"; import type { ApiManagerProxy } from "../../../lib/mtproto/mtprotoworker";
import $rootScope from "../../../lib/rootScope"; import type { AppMessagesManager } from "../../../lib/appManagers/appMessagesManager";
import type { MyDialogFilter } from "../../../lib/storages/filters";
import type { AppPeersManager } from "../../../lib/appManagers/appPeersManager";
import type { AppSidebarLeft } from "..";
import type { DialogFilterSuggested, DialogFilter } from "../../../layer";
import type _$rootScope from "../../../lib/rootScope";
export default class AppChatFoldersTab implements SliderTab { export default class AppChatFoldersTab implements SliderTab {
public container: HTMLElement; public container: HTMLElement;
@ -21,6 +22,10 @@ export default class AppChatFoldersTab implements SliderTab {
private filtersRendered: {[filterID: number]: HTMLElement} = {}; private filtersRendered: {[filterID: number]: HTMLElement} = {};
constructor(private appMessagesManager: AppMessagesManager, private appPeersManager: AppPeersManager, private appSidebarLeft: AppSidebarLeft, private apiManager: ApiManagerProxy, private $rootScope: typeof _$rootScope) {
}
private renderFolder(dialogFilter: DialogFilterSuggested | DialogFilter | MyDialogFilter, container?: HTMLElement, div: HTMLElement = document.createElement('div')) { private renderFolder(dialogFilter: DialogFilterSuggested | DialogFilter | MyDialogFilter, container?: HTMLElement, div: HTMLElement = document.createElement('div')) {
let filter: DialogFilter | MyDialogFilter; let filter: DialogFilter | MyDialogFilter;
let description = ''; let description = '';
@ -35,7 +40,7 @@ export default class AppChatFoldersTab implements SliderTab {
const filterID = filter.id; const filterID = filter.id;
if(!this.filtersRendered.hasOwnProperty(filter.id)) { if(!this.filtersRendered.hasOwnProperty(filter.id)) {
div.addEventListener('click', () => { div.addEventListener('click', () => {
appSidebarLeft.editFolderTab.open(appMessagesManager.filtersStorage.filters[filterID]); this.appSidebarLeft.editFolderTab.open(this.appMessagesManager.filtersStorage.filters[filterID]);
}); });
} }
@ -60,11 +65,11 @@ export default class AppChatFoldersTab implements SliderTab {
else if(pFlags.exclude_archived) description += 'Unarchived'; else if(pFlags.exclude_archived) description += 'Unarchived';
d.push(description); d.push(description);
} else { } else {
const folder = appMessagesManager.dialogsStorage.getFolder(filter.id); const folder = this.appMessagesManager.dialogsStorage.getFolder(filter.id);
let chats = 0, channels = 0, groups = 0; let chats = 0, channels = 0, groups = 0;
for(const dialog of folder) { for(const dialog of folder) {
if(appPeersManager.isAnyGroup(dialog.peerID)) groups++; if(this.appPeersManager.isAnyGroup(dialog.peerID)) groups++;
else if(appPeersManager.isBroadcast(dialog.peerID)) channels++; else if(this.appPeersManager.isBroadcast(dialog.peerID)) channels++;
else chats++; else chats++;
} }
@ -83,7 +88,11 @@ export default class AppChatFoldersTab implements SliderTab {
`; `;
ripple(div); ripple(div);
if(container) container.append(div); if((filter as MyDialogFilter).hasOwnProperty('orderIndex')) {
// ! header will be at 0 index
positionElementByIndex(div, div.parentElement || container, (filter as MyDialogFilter).orderIndex);
} else if(container) container.append(div);
return div; return div;
} }
@ -98,7 +107,7 @@ export default class AppChatFoldersTab implements SliderTab {
if(Object.keys(this.filtersRendered).length >= 10) { if(Object.keys(this.filtersRendered).length >= 10) {
toast('Sorry, you can\'t create more folders.'); toast('Sorry, you can\'t create more folders.');
} else { } else {
appSidebarLeft.editFolderTab.open(); this.appSidebarLeft.editFolderTab.open();
} }
}); });
@ -112,14 +121,13 @@ export default class AppChatFoldersTab implements SliderTab {
this.animation = player; this.animation = player;
}); });
appMessagesManager.filtersStorage.getDialogFilters().then(filters => { this.appMessagesManager.filtersStorage.getDialogFilters().then(filters => {
for(const filterID in filters) { for(const filter of filters) {
const filter = filters[filterID];
this.renderFolder(filter, this.foldersContainer); this.renderFolder(filter, this.foldersContainer);
} }
}); });
$rootScope.$on('filter_update', (e) => { this.$rootScope.$on('filter_update', (e) => {
const filter = e.detail; const filter = e.detail;
if(this.filtersRendered.hasOwnProperty(filter.id)) { if(this.filtersRendered.hasOwnProperty(filter.id)) {
this.renderFolder(filter, null, this.filtersRendered[filter.id]); this.renderFolder(filter, null, this.filtersRendered[filter.id]);
@ -130,7 +138,7 @@ export default class AppChatFoldersTab implements SliderTab {
this.getSuggestedFilters(); this.getSuggestedFilters();
}); });
$rootScope.$on('filter_delete', (e) => { this.$rootScope.$on('filter_delete', (e) => {
const filter = e.detail; const filter = e.detail;
if(this.filtersRendered.hasOwnProperty(filter.id)) { if(this.filtersRendered.hasOwnProperty(filter.id)) {
/* for(const suggested of this.suggestedFilters) { /* for(const suggested of this.suggestedFilters) {
@ -145,11 +153,19 @@ export default class AppChatFoldersTab implements SliderTab {
} }
}); });
this.$rootScope.$on('filter_order', (e) => {
const order = e.detail;
order.forEach((filterID, idx) => {
const div = this.filtersRendered[filterID];
positionElementByIndex(div, div.parentElement, idx + 1); // ! + 1 due to header
});
});
this.getSuggestedFilters(); this.getSuggestedFilters();
} }
private getSuggestedFilters() { private getSuggestedFilters() {
apiManager.invokeApi('messages.getSuggestedDialogFilters').then(suggestedFilters => { this.apiManager.invokeApi('messages.getSuggestedDialogFilters').then(suggestedFilters => {
this.suggestedContainer.style.display = suggestedFilters.length ? '' : 'none'; this.suggestedContainer.style.display = suggestedFilters.length ? '' : 'none';
Array.from(this.suggestedContainer.children).slice(1).forEach(el => el.remove()); Array.from(this.suggestedContainer.children).slice(1).forEach(el => el.remove());
@ -171,7 +187,7 @@ export default class AppChatFoldersTab implements SliderTab {
button.setAttribute('disabled', 'true'); button.setAttribute('disabled', 'true');
appMessagesManager.filtersStorage.createDialogFilter(filter.filter as any).then(bool => { this.appMessagesManager.filtersStorage.createDialogFilter(filter.filter as any).then(bool => {
if(bool) { if(bool) {
div.remove(); div.remove();
} }

3
src/components/sidebarLeft/tabs/editFolder.ts

@ -1,12 +1,13 @@
import appSidebarLeft, { AppSidebarLeft } from ".."; import appSidebarLeft, { AppSidebarLeft } from "..";
import { deepEqual, copy } from "../../../helpers/object"; import { deepEqual, copy } from "../../../helpers/object";
import appDialogsManager from "../../../lib/appManagers/appDialogsManager"; import appDialogsManager from "../../../lib/appManagers/appDialogsManager";
import appMessagesManager, { MyDialogFilter as DialogFilter } from "../../../lib/appManagers/appMessagesManager"; import { MyDialogFilter as DialogFilter } from "../../../lib/storages/filters";
import lottieLoader, { RLottiePlayer } from "../../../lib/lottieLoader"; import lottieLoader, { RLottiePlayer } from "../../../lib/lottieLoader";
import { parseMenuButtonsTo } from "../../misc"; import { parseMenuButtonsTo } from "../../misc";
import { ripple } from "../../ripple"; import { ripple } from "../../ripple";
import { SliderTab } from "../../slider"; import { SliderTab } from "../../slider";
import { toast } from "../../toast"; import { toast } from "../../toast";
import appMessagesManager from "../../../lib/appManagers/appMessagesManager";
const MAX_FOLDER_NAME_LENGTH = 12; const MAX_FOLDER_NAME_LENGTH = 12;

2
src/components/sidebarLeft/tabs/includedChats.ts

@ -4,7 +4,7 @@ import appSidebarLeft, { AppSidebarLeft } from "..";
import appDialogsManager from "../../../lib/appManagers/appDialogsManager"; import appDialogsManager from "../../../lib/appManagers/appDialogsManager";
import appPeersManager from "../../../lib/appManagers/appPeersManager"; import appPeersManager from "../../../lib/appManagers/appPeersManager";
import appUsersManager from "../../../lib/appManagers/appUsersManager"; import appUsersManager from "../../../lib/appManagers/appUsersManager";
import { MyDialogFilter as DialogFilter } from "../../../lib/appManagers/appMessagesManager"; import { MyDialogFilter as DialogFilter } from "../../../lib/storages/filters";
import $rootScope from "../../../lib/rootScope"; import $rootScope from "../../../lib/rootScope";
import { copy } from "../../../helpers/object"; import { copy } from "../../../helpers/object";

2
src/components/slider.ts

@ -13,7 +13,7 @@ export default class SidebarSlider {
protected _selectTab: (id: number) => void; protected _selectTab: (id: number) => void;
public historyTabIDs: number[] = []; public historyTabIDs: number[] = [];
constructor(public sidebarEl: HTMLElement, public tabs: {[id: number]: SliderTab}, canHideFirst = false) { constructor(public sidebarEl: HTMLElement, public tabs: {[id: number]: SliderTab} = {}, canHideFirst = false) {
this._selectTab = horizontalMenu(null, this.sidebarEl.querySelector('.sidebar-slider') as HTMLDivElement, null, null, TRANSITION_TIME); this._selectTab = horizontalMenu(null, this.sidebarEl.querySelector('.sidebar-slider') as HTMLDivElement, null, null, TRANSITION_TIME);
if(!canHideFirst) { if(!canHideFirst) {
this._selectTab(0); this._selectTab(0);

24
src/components/transition.ts

@ -31,12 +31,12 @@ const Transition = (content: HTMLElement, type: 'tabs' | 'navigation' | 'zoom-fa
//const deferred: (() => void)[] = []; //const deferred: (() => void)[] = [];
let transitionEndTimeout: number; let transitionEndTimeout: number;
let prevTabContent: HTMLElement = null; let prevTabContent: HTMLElement = null;
let prevId = -1;
const animationFunction = type == 'zoom-fade' ? null : (type == 'tabs' ? slideTabs : slideNavigation); const animationFunction = type == 'zoom-fade' ? null : (type == 'tabs' ? slideTabs : slideNavigation);
const selectTab = (id: number, animate = true) => { function selectTab(id: number, animate = true) {
if(id == prevId) return false; const self = selectTab;
if(id == self.prevId) return false;
//console.log('selectTab id:', id); //console.log('selectTab id:', id);
@ -51,10 +51,10 @@ const Transition = (content: HTMLElement, type: 'tabs' | 'navigation' | 'zoom-fa
tabContent.classList.add('active'); tabContent.classList.add('active');
prevId = id; self.prevId = id;
prevTabContent = tabContent; prevTabContent = tabContent;
if(onTransitionEnd) onTransitionEnd(prevId); if(onTransitionEnd) onTransitionEnd(self.prevId);
return; return;
} }
@ -64,12 +64,12 @@ const Transition = (content: HTMLElement, type: 'tabs' | 'navigation' | 'zoom-fa
} }
content.classList.add('animating'); content.classList.add('animating');
const toRight = prevId < id; const toRight = self.prevId < id;
content.classList.toggle('backwards', !toRight); content.classList.toggle('backwards', !toRight);
if(!tabContent) { if(!tabContent) {
//prevTabContent.classList.remove('active'); //prevTabContent.classList.remove('active');
} else if(prevId != -1) { } else if(self.prevId != -1) {
if(animationFunction) { if(animationFunction) {
animationFunction(tabContent, prevTabContent, toRight); animationFunction(tabContent, prevTabContent, toRight);
} }
@ -79,7 +79,7 @@ const Transition = (content: HTMLElement, type: 'tabs' | 'navigation' | 'zoom-fa
tabContent.classList.add('active'); tabContent.classList.add('active');
} }
const _prevId = prevId; const _prevId = self.prevId;
if(hideTimeouts.hasOwnProperty(id)) clearTimeout(hideTimeouts[id]); if(hideTimeouts.hasOwnProperty(id)) clearTimeout(hideTimeouts[id]);
if(p/* && false */) { if(p/* && false */) {
hideTimeouts[_prevId] = window.setTimeout(() => { hideTimeouts[_prevId] = window.setTimeout(() => {
@ -99,13 +99,13 @@ const Transition = (content: HTMLElement, type: 'tabs' | 'navigation' | 'zoom-fa
if(onTransitionEnd) { if(onTransitionEnd) {
if(transitionEndTimeout) clearTimeout(transitionEndTimeout); if(transitionEndTimeout) clearTimeout(transitionEndTimeout);
transitionEndTimeout = window.setTimeout(() => { transitionEndTimeout = window.setTimeout(() => {
onTransitionEnd(prevId); onTransitionEnd(self.prevId);
transitionEndTimeout = 0; transitionEndTimeout = 0;
}, transitionTime); }, transitionTime);
} }
} }
prevId = id; self.prevId = id;
prevTabContent = tabContent; prevTabContent = tabContent;
/* if(p) { /* if(p) {
@ -115,7 +115,9 @@ const Transition = (content: HTMLElement, type: 'tabs' | 'navigation' | 'zoom-fa
} else { } else {
return Promise.resolve(); return Promise.resolve();
} */ } */
}; }
selectTab.prevId = -1;
return selectTab; return selectTab;
}; };

89
src/lib/appManagers/appDialogsManager.ts

@ -15,7 +15,8 @@ import { RichTextProcessor } from "../richtextprocessor";
import $rootScope from "../rootScope"; import $rootScope from "../rootScope";
import { findUpClassName, positionElementByIndex } from "../../helpers/dom"; import { findUpClassName, positionElementByIndex } from "../../helpers/dom";
import appImManager, { AppImManager } from "./appImManager"; import appImManager, { AppImManager } from "./appImManager";
import appMessagesManager, { Dialog, MyDialogFilter as DialogFilter } from "./appMessagesManager"; import appMessagesManager, { Dialog } from "./appMessagesManager";
import {MyDialogFilter as DialogFilter} from "../storages/filters";
import appPeersManager from './appPeersManager'; import appPeersManager from './appPeersManager';
import appStateManager from "./appStateManager"; import appStateManager from "./appStateManager";
import appUsersManager, { User } from "./appUsersManager"; import appUsersManager, { User } from "./appUsersManager";
@ -35,14 +36,11 @@ type DialogDom = {
}; };
const testScroll = false; const testScroll = false;
//const USEPINNEDDELIMITER = false;
export class AppDialogsManager { export class AppDialogsManager {
public _chatList = document.getElementById('dialogs') as HTMLUListElement; public _chatList = document.getElementById('dialogs') as HTMLUListElement;
public chatList = this._chatList; public chatList = this._chatList;
//public pinnedDelimiter: HTMLDivElement;
public doms: {[peerID: number]: DialogDom} = {}; public doms: {[peerID: number]: DialogDom} = {};
public lastActiveListElement: HTMLElement = null; public lastActiveListElement: HTMLElement = null;
@ -91,12 +89,6 @@ export class AppDialogsManager {
this.allUnreadCount = this.folders.menu.querySelector('.unread-count'); this.allUnreadCount = this.folders.menu.querySelector('.unread-count');
/* if(USEPINNEDDELIMITER) {
this.pinnedDelimiter = document.createElement('div');
this.pinnedDelimiter.classList.add('pinned-delimiter');
this.pinnedDelimiter.appendChild(document.createElement('span'));
} */
this.folders.menuScrollContainer = this.folders.menu.parentElement; this.folders.menuScrollContainer = this.folders.menu.parentElement;
this.scroll = this._scroll = new Scrollable(this.chatsContainer, 'CL', this.chatList, 500); this.scroll = this._scroll = new Scrollable(this.chatsContainer, 'CL', this.chatList, 500);
@ -165,18 +157,17 @@ export class AppDialogsManager {
}); });
$rootScope.$on('dialog_top', (e) => { $rootScope.$on('dialog_top', (e) => {
let dialog: any = e.detail; const dialog = e.detail;
this.setLastMessage(dialog); this.setLastMessage(dialog);
this.setDialogPosition(dialog); this.setDialogPosition(dialog);
//this.setPinnedDelimiter();
this.setFiltersUnreadCount(); this.setFiltersUnreadCount();
}); });
$rootScope.$on('dialog_flush', (e) => { $rootScope.$on('dialog_flush', (e) => {
let peerID: number = e.detail.peerID; const peerID: number = e.detail.peerID;
let dialog = appMessagesManager.getDialogByPeerID(peerID)[0]; const dialog = appMessagesManager.getDialogByPeerID(peerID)[0];
if(dialog) { if(dialog) {
this.setLastMessage(dialog); this.setLastMessage(dialog);
this.validateForFilter(); this.validateForFilter();
@ -192,7 +183,6 @@ export class AppDialogsManager {
this.updateDialog(dialog); this.updateDialog(dialog);
} }
//this.setPinnedDelimiter();
this.validateForFilter(); this.validateForFilter();
this.setFiltersUnreadCount(); this.setFiltersUnreadCount();
}); });
@ -291,16 +281,23 @@ export class AppDialogsManager {
} }
}); });
/* $rootScope.$on('filter_pinned_order', (e) => { $rootScope.$on('filter_order', (e) => {
const {order, id} = e.detail as {order: number[], id: number}; const order = e.detail;
if(this.prevTabID != id) {
return; const containerToAppend = this.folders.menu.firstElementChild as HTMLUListElement;
} order.forEach((filterID) => {
const filter = appMessagesManager.filtersStorage.filters[filterID];
const renderedFilter = this.filtersRendered[filterID];
for(const peerID of order) { positionElementByIndex(renderedFilter.menu, containerToAppend, filter.orderIndex);
this.updateDialog(appMessagesManager.getDialogByPeerID(peerID)[0]); positionElementByIndex(renderedFilter.container, this.folders.container, filter.orderIndex);
});
if(this.filterID) {
const tabIndex = order.indexOf(this.filterID) + 1;
selectTab.prevId = tabIndex;
} }
}); */ });
const foldersScrollable = new ScrollableX(this.folders.menuScrollContainer); const foldersScrollable = new ScrollableX(this.folders.menuScrollContainer);
this.chatsContainer.prepend(this.folders.menuScrollContainer); this.chatsContainer.prepend(this.folders.menuScrollContainer);
@ -330,10 +327,10 @@ export class AppDialogsManager {
//selectTab(0); //selectTab(0);
(this.folders.menu.firstElementChild.firstElementChild as HTMLElement).click(); (this.folders.menu.firstElementChild.firstElementChild as HTMLElement).click();
appStateManager.getState().then((state) => { appStateManager.getState().then((state) => {
const getFiltersPromise = !state.filters || Object.keys(state.filters).length ? appMessagesManager.filtersStorage.getDialogFilters() : Promise.resolve({}); const getFiltersPromise = !state.filters || Object.keys(state.filters).length ? appMessagesManager.filtersStorage.getDialogFilters() : Promise.resolve([]);
getFiltersPromise.then((filters) => { getFiltersPromise.then((filters) => {
for(const filterID in filters) { for(const filter of filters) {
this.addFilter(filters[filterID]); this.addFilter(filter);
} }
}); });
@ -433,7 +430,7 @@ export class AppDialogsManager {
ripple(li); ripple(li);
const containerToAppend = this.folders.menu.firstElementChild as HTMLUListElement; const containerToAppend = this.folders.menu.firstElementChild as HTMLUListElement;
positionElementByIndex(li, containerToAppend, filter.orderIndex + 1); // because 0 is All positionElementByIndex(li, containerToAppend, filter.orderIndex);
//containerToAppend.append(li); //containerToAppend.append(li);
const ul = document.createElement('ul'); const ul = document.createElement('ul');
@ -441,7 +438,7 @@ export class AppDialogsManager {
div.append(ul); div.append(ul);
div.dataset.filterID = '' + filter.id; div.dataset.filterID = '' + filter.id;
//this.folders.container.append(div); //this.folders.container.append(div);
positionElementByIndex(div, this.folders.container, filter.orderIndex + 1); // because 0 is All positionElementByIndex(div, this.folders.container, filter.orderIndex);
this.chatLists[filter.id] = ul; this.chatLists[filter.id] = ul;
this.setListClickListener(ul, null, true); this.setListClickListener(ul, null, true);
@ -608,42 +605,6 @@ export class AppDialogsManager {
} }
} }
/* public setPinnedDelimiter() {
if(!USEPINNEDDELIMITER) return;
let index = -1;
let dialogs = appMessagesManager.dialogsStorage.getFolder(0);
for(let dialog of dialogs) {
if(dialog.pFlags?.pinned) {
index++;
}
}
let currentIndex = (this.pinnedDelimiter.parentElement && whichChild(this.pinnedDelimiter.parentElement)) ?? -1;
if(index == currentIndex) return;
let children = this.chatList.children;
let modifying: HTMLElement[] = [];
if(currentIndex != -1 && children.length > currentIndex) {
let li = children[currentIndex] as HTMLElement;
modifying.push(li);
}
if(index != -1 && children.length > index) {
let li = children[index] as HTMLElement;
modifying.push(li);
li.append(this.pinnedDelimiter);
} else {
this.pinnedDelimiter.remove();
}
modifying.forEach(elem => {
this.scroll.updateElement(elem);
});
} */
public setLastMessage(dialog: any, lastMessage?: any, dom?: DialogDom, highlightWord?: string) { public setLastMessage(dialog: any, lastMessage?: any, dom?: DialogDom, highlightWord?: string) {
if(!lastMessage) { if(!lastMessage) {
lastMessage = appMessagesManager.getMessage(dialog.top_message); lastMessage = appMessagesManager.getMessage(dialog.top_message);

412
src/lib/appManagers/appMessagesManager.ts

@ -1,4 +1,3 @@
import { MessageRender } from "../../components/chat/messageRender";
import ProgressivePreloader from "../../components/preloader"; import ProgressivePreloader from "../../components/preloader";
import { listMergeSorted } from "../../helpers/array"; import { listMergeSorted } from "../../helpers/array";
import { CancellablePromise, deferredPromise } from "../../helpers/cancellablePromise"; import { CancellablePromise, deferredPromise } from "../../helpers/cancellablePromise";
@ -6,19 +5,21 @@ import { tsNow } from "../../helpers/date";
import { copy, defineNotNumerableProperties, deepEqual, safeReplaceObject, getObjectKeysAndSort } from "../../helpers/object"; import { copy, defineNotNumerableProperties, deepEqual, safeReplaceObject, getObjectKeysAndSort } from "../../helpers/object";
import { randomLong } from "../../helpers/random"; import { randomLong } from "../../helpers/random";
import { splitStringByLength, limitSymbols } from "../../helpers/string"; import { splitStringByLength, limitSymbols } from "../../helpers/string";
import { Dialog as MTDialog, DialogFilter, DialogPeer, DocumentAttribute, InputMessage, Message, MessageAction, MessageEntity, MessagesDialogs, MessagesFilter, MessagesMessages, MessagesPeerDialogs, MethodDeclMap, PhotoSize, SendMessageAction, Update } from "../../layer"; import { Dialog as MTDialog, DialogPeer, DocumentAttribute, InputMessage, Message, MessageAction, MessageEntity, MessagesDialogs, MessagesFilter, MessagesMessages, MessagesPeerDialogs, MethodDeclMap, PhotoSize, SendMessageAction, Update } from "../../layer";
import { InvokeApiOptions, Modify } from "../../types"; import { InvokeApiOptions } from "../../types";
import { langPack } from "../langPack"; import { langPack } from "../langPack";
import { logger, LogLevels } from "../logger"; import { logger, LogLevels } from "../logger";
import type { ApiFileManager } from '../mtproto/apiFileManager'; import type { ApiFileManager } from '../mtproto/apiFileManager';
//import apiManager from '../mtproto/apiManager'; //import apiManager from '../mtproto/apiManager';
import apiManager from '../mtproto/mtprotoworker'; import apiManager from '../mtproto/mtprotoworker';
import referenceDatabase, { ReferenceContext } from "../mtproto/referenceDatabase"; import referenceDatabase, { ReferenceContext } from "../mtproto/referenceDatabase";
import { default as ServerTimeManager, default as serverTimeManager } from "../mtproto/serverTimeManager"; import serverTimeManager from "../mtproto/serverTimeManager";
import { RichTextProcessor } from "../richtextprocessor"; import { RichTextProcessor } from "../richtextprocessor";
import $rootScope from "../rootScope"; import $rootScope from "../rootScope";
import searchIndexManager from '../searchIndexManager'; import searchIndexManager from '../searchIndexManager';
import AppStorage from '../storage'; import AppStorage from '../storage';
import DialogsStorage from "../storages/dialogs";
import FiltersStorage from "../storages/filters";
//import { telegramMeWebService } from "../mtproto/mtproto"; //import { telegramMeWebService } from "../mtproto/mtproto";
import apiUpdatesManager from "./apiUpdatesManager"; import apiUpdatesManager from "./apiUpdatesManager";
import appChatsManager from "./appChatsManager"; import appChatsManager from "./appChatsManager";
@ -32,7 +33,6 @@ import appStateManager from "./appStateManager";
import appUsersManager from "./appUsersManager"; import appUsersManager from "./appUsersManager";
import appWebPagesManager from "./appWebPagesManager"; import appWebPagesManager from "./appWebPagesManager";
//console.trace('include'); //console.trace('include');
// TODO: если удалить сообщение в непрогруженном диалоге, то при обновлении, из-за стейта, последнего сообщения в чатлисте не будет // TODO: если удалить сообщение в непрогруженном диалоге, то при обновлении, из-за стейта, последнего сообщения в чатлисте не будет
// TODO: если удалить диалог находясь в папке, то он не удалится из папки и будет виден в настройках // TODO: если удалить диалог находясь в папке, то он не удалится из папки и будет виден в настройках
@ -59,390 +59,7 @@ export type HistoryResult = {
export type Dialog = MTDialog.dialog; export type Dialog = MTDialog.dialog;
export class DialogsStorage { export type MyMessage = Message.message | Message.messageService;
public dialogs: {[peerID: string]: Dialog} = {};
public byFolders: {[folderID: number]: Dialog[]} = {};
public allDialogsLoaded: {[folder_id: number]: boolean} = {};
public dialogsOffsetDate: {[folder_id: number]: number} = {};
public pinnedOrders: {[folder_id: number]: number[]} = {
0: [],
1: []
};
public dialogsNum = 0;
public getFolder(id: number) {
if(id <= 1) {
return this.byFolders[id] ?? (this.byFolders[id] = []);
}
const dialogs: {dialog: Dialog, index: number}[] = [];
const filter = appMessagesManager.filtersStorage.filters[id];
for(const peerID in this.dialogs) {
const dialog = this.dialogs[peerID];
if(appMessagesManager.filtersStorage.testDialogForFilter(dialog, filter)) {
let index: number;
const pinnedIndex = filter.pinned_peers.indexOf(dialog.peerID);
if(pinnedIndex !== -1) {
index = this.generateDialogIndex(this.generateDialogPinnedDateByIndex(filter.pinned_peers.length - 1 - pinnedIndex));
} else if(dialog.pFlags?.pinned) {
index = this.generateIndexForDialog(dialog, true);
} else {
index = dialog.index;
}
dialogs.push({dialog, index});
}
}
dialogs.sort((a, b) => b.index - a.index);
return dialogs.map(d => d.dialog);
}
public getDialog(peerID: number, folderID?: number): [Dialog, number] | [] {
const folders: Dialog[][] = [];
if(folderID === undefined) {
const dialogs = this.byFolders;
for(const folderID in dialogs) {
folders.push(dialogs[folderID]);
}
} else {
folders.push(this.getFolder(folderID));
}
for(let folder of folders) {
const index = folder.findIndex(dialog => dialog.peerID == peerID);
if(index !== -1) {
return [folder[index], index];
}
}
return [];
}
/*
var date = Date.now() / 1000 | 0;
var m = date * 0x10000;
var k = (date + 1) * 0x10000;
k - m;
65536
*/
public generateDialogIndex(date?: number) {
if(date === undefined) {
date = tsNow(true) + serverTimeManager.serverTimeOffset;
}
return (date * 0x10000) + ((++this.dialogsNum) & 0xFFFF);
}
public generateIndexForDialog(dialog: Dialog, justReturn = false) {
const channelID = appPeersManager.isChannel(dialog.peerID) ? -dialog.peerID : 0;
const mid = appMessagesIDsManager.getFullMessageID(dialog.top_message, channelID);
const message = appMessagesManager.getMessage(mid);
let topDate = (message as Message.message).date || Date.now() / 1000;
if(channelID) {
const channel = appChatsManager.getChat(channelID);
if(!topDate || channel.date && channel.date > topDate) {
topDate = channel.date;
}
}
const savedDraft: any = {};// DraftsManager.saveDraft(peerID, dialog.draft); // warning
if(savedDraft && savedDraft.date > topDate) {
topDate = savedDraft.date;
}
if(dialog.pFlags.pinned && !justReturn) {
topDate = this.generateDialogPinnedDate(dialog);
//this.log('topDate', peerID, topDate);
}
const index = this.generateDialogIndex(topDate);
if(justReturn) return index;
dialog.index = index;
}
public generateDialogPinnedDateByIndex(pinnedIndex: number) {
return 0x7fff0000 + (pinnedIndex & 0xFFFF); // 0xFFFF - потому что в папках может быть бесконечное число пиннедов
}
public generateDialogPinnedDate(dialog: Dialog) {
const order = this.pinnedOrders[dialog.folder_id];
const foundIndex = order.indexOf(dialog.peerID);
const pinnedIndex = foundIndex === -1 ? order.push(dialog.peerID) - 1 : foundIndex;
return this.generateDialogPinnedDateByIndex(pinnedIndex);
}
public pushDialog(dialog: Dialog, offsetDate?: number) {
const dialogs = this.getFolder(dialog.folder_id);
const pos = dialogs.findIndex(d => d.peerID == dialog.peerID);
if(pos !== -1) {
dialogs.splice(pos, 1);
}
//if(!this.dialogs[dialog.peerID]) {
this.dialogs[dialog.peerID] = dialog;
//}
if(offsetDate &&
!dialog.pFlags.pinned &&
(!this.dialogsOffsetDate[dialog.folder_id] || offsetDate < this.dialogsOffsetDate[dialog.folder_id])) {
if(pos !== -1) {
// So the dialog jumped to the last position
return false;
}
this.dialogsOffsetDate[dialog.folder_id] = offsetDate;
}
const index = dialog.index;
const len = dialogs.length;
if(!len || index < dialogs[len - 1].index) {
dialogs.push(dialog);
} else if(index >= dialogs[0].index) {
dialogs.unshift(dialog);
} else {
for(let i = 0; i < len; i++) {
if(index > dialogs[i].index) {
dialogs.splice(i, 0, dialog);
break;
}
}
}
}
public dropDialog(peerID: number): [Dialog, number] | [] {
const foundDialog = this.getDialog(peerID);
if(foundDialog[0]) {
this.byFolders[foundDialog[0].folder_id].splice(foundDialog[1], 1);
delete this.dialogs[peerID];
}
return foundDialog;
}
}
export type MyDialogFilter = Modify<DialogFilter, {
pinned_peers: number[],
include_peers: number[],
exclude_peers: number[],
orderIndex?: number
}>;
export class FiltersStorage {
public filters: {[filterID: string]: MyDialogFilter} = {};
public orderIndex = 0;
constructor() {
$rootScope.$on('apiUpdate', (e) => {
this.handleUpdate(e.detail);
});
}
public handleUpdate(update: Update) {
switch(update._) {
case 'updateDialogFilter': {
//console.log('updateDialogFilter', update);
if(update.filter) {
this.saveDialogFilter(update.filter as any);
} else if(this.filters[update.id]) { // Папка удалена
//this.getDialogFilters(true);
$rootScope.$broadcast('filter_delete', this.filters[update.id]);
delete this.filters[update.id];
}
break;
}
}
}
public testDialogForFilter(dialog: Dialog, filter: MyDialogFilter) {
// exclude_peers
for(const peerID of filter.exclude_peers) {
if(peerID == dialog.peerID) {
return false;
}
}
// include_peers
for(const peerID of filter.include_peers) {
if(peerID == dialog.peerID) {
return true;
}
}
const pFlags = filter.pFlags;
// exclude_archived
if(pFlags.exclude_archived && dialog.folder_id == 1) {
return false;
}
// exclude_read
if(pFlags.exclude_read && !dialog.unread_count) {
return false;
}
// exclude_muted
if(pFlags.exclude_muted) {
const isMuted = (dialog.notify_settings?.mute_until * 1000) > Date.now();
if(isMuted) {
return false;
}
}
const peerID = dialog.peerID;
if(peerID < 0) {
// broadcasts
if(pFlags.broadcasts && appPeersManager.isBroadcast(peerID)) {
return true;
}
// groups
if(pFlags.groups && appPeersManager.isAnyGroup(peerID)) {
return true;
}
} else {
// bots
if(appPeersManager.isBot(peerID)) {
return !!pFlags.bots;
}
// non_contacts
if(pFlags.non_contacts && !appUsersManager.contactsList.has(peerID)) {
return true;
}
// contacts
if(pFlags.contacts && appUsersManager.contactsList.has(peerID)) {
return true;
}
}
return false;
}
public toggleDialogPin(peerID: number, filterID: number) {
const filter = this.filters[filterID];
const wasPinned = filter.pinned_peers.findAndSplice(p => p == peerID);
if(!wasPinned) {
filter.pinned_peers.unshift(peerID);
}
return this.updateDialogFilter(filter);
}
public createDialogFilter(filter: MyDialogFilter) {
let maxID = Math.max(1, ...Object.keys(this.filters).map(i => +i));
filter = copy(filter);
filter.id = maxID + 1;
return this.updateDialogFilter(filter);
}
public updateDialogFilter(filter: MyDialogFilter, remove = false) {
const flags = remove ? 0 : 1;
return apiManager.invokeApi('messages.updateDialogFilter', {
flags,
id: filter.id,
filter: remove ? undefined : this.getOutputDialogFilter(filter)
}).then((bool: boolean) => { // возможно нужна проверка и откат, если результат не ТРУ
//console.log('updateDialogFilter bool:', bool);
if(bool) {
/* if(!this.filters[filter.id]) {
this.saveDialogFilter(filter);
}
$rootScope.$broadcast('filter_update', filter); */
this.handleUpdate({
_: 'updateDialogFilter',
id: filter.id,
filter: remove ? undefined : filter as any
});
}
return bool;
});
}
public getOutputDialogFilter(filter: MyDialogFilter) {
const c: MyDialogFilter = copy(filter);
['pinned_peers', 'exclude_peers', 'include_peers'].forEach(key => {
// @ts-ignore
c[key] = c[key].map((peerID: number) => appPeersManager.getInputPeerByID(peerID));
});
c.include_peers.forEachReverse((peerID, idx) => {
if(c.pinned_peers.includes(peerID)) {
c.include_peers.splice(idx, 1);
}
});
return c as any as DialogFilter;
}
public async getDialogFilters(overwrite = false) {
if(Object.keys(this.filters).length && !overwrite) {
return this.filters;
}
const filters = await apiManager.invokeApi('messages.getDialogFilters');
for(const filter of filters) {
this.saveDialogFilter(filter as any as MyDialogFilter, false);
}
//console.log(this.filters);
return this.filters;
}
public saveDialogFilter(filter: MyDialogFilter, update = true) {
['pinned_peers', 'exclude_peers', 'include_peers'].forEach(key => {
// @ts-ignore
filter[key] = filter[key].map((peer: any) => appPeersManager.getPeerID(peer));
});
filter.include_peers.forEachReverse((peerID, idx) => {
if(filter.pinned_peers.includes(peerID)) {
filter.include_peers.splice(idx, 1);
}
});
filter.include_peers = filter.pinned_peers.concat(filter.include_peers);
if(this.filters[filter.id]) {
Object.assign(this.filters[filter.id], filter);
} else {
this.filters[filter.id] = filter;
}
this.setOrderIndex(filter);
if(update) {
$rootScope.$broadcast('filter_update', filter);
}
}
public setOrderIndex(filter: MyDialogFilter) {
if(filter.hasOwnProperty('orderIndex')) {
if(filter.orderIndex > this.orderIndex) {
this.orderIndex = filter.orderIndex;
}
} else {
filter.orderIndex = this.orderIndex++;
}
}
}
type MyMessage = Message.message | Message.messageService;
type MyInputMessagesFilter = 'inputMessagesFilterEmpty' type MyInputMessagesFilter = 'inputMessagesFilterEmpty'
| 'inputMessagesFilterPhotos' | 'inputMessagesFilterPhotos'
| 'inputMessagesFilterPhotoVideo' | 'inputMessagesFilterPhotoVideo'
@ -514,10 +131,13 @@ export class AppMessagesManager {
private log = logger('MESSAGES'/* , LogLevels.error | LogLevels.debug | LogLevels.log | LogLevels.warn */); private log = logger('MESSAGES'/* , LogLevels.error | LogLevels.debug | LogLevels.log | LogLevels.warn */);
public dialogsStorage = new DialogsStorage(); public dialogsStorage: DialogsStorage;
public filtersStorage = new FiltersStorage(); public filtersStorage: FiltersStorage;
constructor() { constructor() {
this.dialogsStorage = new DialogsStorage(this, appMessagesIDsManager, appChatsManager, appPeersManager, serverTimeManager);
this.filtersStorage = new FiltersStorage(appPeersManager, appUsersManager, /* apiManager, */ $rootScope);
$rootScope.$on('apiUpdate', (e) => { $rootScope.$on('apiUpdate', (e) => {
this.handleUpdate(e.detail); this.handleUpdate(e.detail);
}); });
@ -970,7 +590,7 @@ export class AppMessagesManager {
const isDocument = !(file instanceof File) && !(file instanceof Blob); const isDocument = !(file instanceof File) && !(file instanceof Blob);
let caption = options.caption || ''; let caption = options.caption || '';
const date = tsNow(true) + ServerTimeManager.serverTimeOffset; const date = tsNow(true) + serverTimeManager.serverTimeOffset;
this.log('sendFile', file, fileType); this.log('sendFile', file, fileType);
@ -1321,7 +941,7 @@ export class AppMessagesManager {
let caption = options.caption || ''; let caption = options.caption || '';
let date = tsNow(true) + ServerTimeManager.serverTimeOffset; let date = tsNow(true) + serverTimeManager.serverTimeOffset;
if(caption) { if(caption) {
let entities = options.entities || []; let entities = options.entities || [];
@ -1707,7 +1327,7 @@ export class AppMessagesManager {
from_id: appPeersManager.getOutputPeer(fromID), from_id: appPeersManager.getOutputPeer(fromID),
peer_id: appPeersManager.getOutputPeer(peerID), peer_id: appPeersManager.getOutputPeer(peerID),
pFlags: pFlags, pFlags: pFlags,
date: tsNow(true) + ServerTimeManager.serverTimeOffset, date: tsNow(true) + serverTimeManager.serverTimeOffset,
message: '', message: '',
media: media, media: media,
random_id: randomIDS, random_id: randomIDS,
@ -3193,7 +2813,7 @@ export class AppMessagesManager {
var offsetMessage = maxID && this.getMessage(maxID); var offsetMessage = maxID && this.getMessage(maxID);
if(offsetMessage && offsetMessage.date) { if(offsetMessage && offsetMessage.date) {
offsetDate = offsetMessage.date + ServerTimeManager.serverTimeOffset; offsetDate = offsetMessage.date + serverTimeManager.serverTimeOffset;
offsetID = offsetMessage.id; offsetID = offsetMessage.id;
offsetPeerID = this.getMessagePeer(offsetMessage); offsetPeerID = this.getMessagePeer(offsetMessage);
} }
@ -3484,7 +3104,7 @@ export class AppMessagesManager {
} }
public handleUpdate(update: Update) { public handleUpdate(update: Update) {
this.log.debug('AMM: handleUpdate:', update._); this.log.debug('handleUpdate', update._);
switch(update._) { switch(update._) {
case 'updateMessageID': { case 'updateMessageID': {
const randomID = update.random_id; const randomID = update.random_id;

4
src/lib/appManagers/appStateManager.ts

@ -1,4 +1,4 @@
import type { Dialog, DialogsStorage, FiltersStorage } from './appMessagesManager'; import type { Dialog } from './appMessagesManager';
import type { AppStickersManager } from './appStickersManager'; import type { AppStickersManager } from './appStickersManager';
import { App, MOUNT_CLASS_TO, UserAuth } from '../mtproto/mtproto_config'; import { App, MOUNT_CLASS_TO, UserAuth } from '../mtproto/mtproto_config';
import EventListenerBase from '../../helpers/eventListenerBase'; import EventListenerBase from '../../helpers/eventListenerBase';
@ -9,6 +9,8 @@ import type { AppUsersManager } from './appUsersManager';
import type { AppChatsManager } from './appChatsManager'; import type { AppChatsManager } from './appChatsManager';
import type { AuthState } from '../../types'; import type { AuthState } from '../../types';
import type { AppMessagesIDsManager } from './appMessagesIDsManager'; import type { AppMessagesIDsManager } from './appMessagesIDsManager';
import type FiltersStorage from '../storages/filters';
import type DialogsStorage from '../storages/dialogs';
const REFRESH_EVERY = 24 * 60 * 60 * 1000; // 1 day const REFRESH_EVERY = 24 * 60 * 60 * 1000; // 1 day
const STATE_VERSION = App.version; const STATE_VERSION = App.version;

16
src/lib/appManagers/appUsersManager.ts

@ -24,6 +24,7 @@ export class AppUsersManager {
public contactsIndex = searchIndexManager.createIndex(); public contactsIndex = searchIndexManager.createIndex();
public contactsFillPromise: Promise<Set<number>>; public contactsFillPromise: Promise<Set<number>>;
public contactsList: Set<number> = new Set(); public contactsList: Set<number> = new Set();
private updatedContactsList = false;
public getTopPeersPromise: Promise<number[]>; public getTopPeersPromise: Promise<number[]>;
@ -107,10 +108,11 @@ export class AppUsersManager {
appStateManager.getState().then((state) => { appStateManager.getState().then((state) => {
const contactsList = state.contactsList; const contactsList = state.contactsList;
if(contactsList && Array.isArray(contactsList) && contactsList.length) { if(contactsList && Array.isArray(contactsList)) {
contactsList.forEach(userID => { contactsList.forEach(userID => {
this.pushContact(userID); this.pushContact(userID);
}); });
this.contactsFillPromise = Promise.resolve(this.contactsList); this.contactsFillPromise = Promise.resolve(this.contactsList);
} }
@ -119,13 +121,13 @@ export class AppUsersManager {
} }
public fillContacts() { public fillContacts() {
if(this.contactsFillPromise) { if(this.contactsFillPromise && this.updatedContactsList) {
return this.contactsFillPromise; return this.contactsFillPromise;
} }
return this.contactsFillPromise = apiManager.invokeApi('contacts.getContacts', { this.updatedContactsList = true;
hash: 0
}).then((result) => { const promise = apiManager.invokeApi('contacts.getContacts').then((result) => {
if(result._ == 'contacts.contacts') { if(result._ == 'contacts.contacts') {
this.saveApiUsers(result.users); this.saveApiUsers(result.users);
@ -134,8 +136,12 @@ export class AppUsersManager {
}); });
} }
this.contactsFillPromise = promise;
return this.contactsList; return this.contactsList;
}); });
return this.contactsFillPromise || (this.contactsFillPromise = promise);
} }
public async resolveUsername(username: string) { public async resolveUsername(username: string) {

2
src/lib/mtproto/mtprotoworker.ts

@ -22,7 +22,7 @@ type Task = {
const USEWORKERASWORKER = true; const USEWORKERASWORKER = true;
class ApiManagerProxy extends CryptoWorkerMethods { export class ApiManagerProxy extends CryptoWorkerMethods {
public worker: Worker; public worker: Worker;
public postMessage: (...args: any[]) => void; public postMessage: (...args: any[]) => void;
private afterMessageIDTemp = 0; private afterMessageIDTemp = 0;

4
src/lib/rootScope.ts

@ -1,7 +1,8 @@
import type { StickerSet, Update } from "../layer"; import type { StickerSet, Update } from "../layer";
import type { MyDocument } from "./appManagers/appDocsManager"; import type { MyDocument } from "./appManagers/appDocsManager";
import type { AppMessagesManager, Dialog, MyDialogFilter } from "./appManagers/appMessagesManager"; import type { AppMessagesManager, Dialog } from "./appManagers/appMessagesManager";
import type { Poll, PollResults } from "./appManagers/appPollsManager"; import type { Poll, PollResults } from "./appManagers/appPollsManager";
import type { MyDialogFilter } from "./storages/filters";
import { MOUNT_CLASS_TO } from "./mtproto/mtproto_config"; import { MOUNT_CLASS_TO } from "./mtproto/mtproto_config";
type BroadcastEvents = { type BroadcastEvents = {
@ -12,6 +13,7 @@ type BroadcastEvents = {
'filter_delete': MyDialogFilter, 'filter_delete': MyDialogFilter,
'filter_update': MyDialogFilter, 'filter_update': MyDialogFilter,
'filter_order': number[],
'dialog_draft': {peerID: number, draft: any, index: number}, 'dialog_draft': {peerID: number, draft: any, index: number},
'dialog_unread': {peerID: number, count?: number}, 'dialog_unread': {peerID: number, count?: number},

180
src/lib/storages/dialogs.ts

@ -0,0 +1,180 @@
import { tsNow } from "../../helpers/date";
import type { Message } from "../../layer";
import type { AppChatsManager } from "../appManagers/appChatsManager";
import type { AppMessagesIDsManager } from "../appManagers/appMessagesIDsManager";
import type { AppMessagesManager, Dialog } from "../appManagers/appMessagesManager";
import type { AppPeersManager } from "../appManagers/appPeersManager";
import type { ServerTimeManager } from "../mtproto/serverTimeManager";
export default class DialogsStorage {
public dialogs: {[peerID: string]: Dialog} = {};
public byFolders: {[folderID: number]: Dialog[]} = {};
public allDialogsLoaded: {[folder_id: number]: boolean} = {};
public dialogsOffsetDate: {[folder_id: number]: number} = {};
public pinnedOrders: {[folder_id: number]: number[]} = {
0: [],
1: []
};
public dialogsNum = 0;
constructor(private appMessagesManager: AppMessagesManager, private appMessagesIDsManager: AppMessagesIDsManager, private appChatsManager: AppChatsManager, private appPeersManager: AppPeersManager, private serverTimeManager: ServerTimeManager) {
}
public getFolder(id: number) {
if(id <= 1) {
return this.byFolders[id] ?? (this.byFolders[id] = []);
}
const dialogs: {dialog: Dialog, index: number}[] = [];
const filter = this.appMessagesManager.filtersStorage.filters[id];
for(const peerID in this.dialogs) {
const dialog = this.dialogs[peerID];
if(this.appMessagesManager.filtersStorage.testDialogForFilter(dialog, filter)) {
let index: number;
const pinnedIndex = filter.pinned_peers.indexOf(dialog.peerID);
if(pinnedIndex !== -1) {
index = this.generateDialogIndex(this.generateDialogPinnedDateByIndex(filter.pinned_peers.length - 1 - pinnedIndex));
} else if(dialog.pFlags?.pinned) {
index = this.generateIndexForDialog(dialog, true);
} else {
index = dialog.index;
}
dialogs.push({dialog, index});
}
}
dialogs.sort((a, b) => b.index - a.index);
return dialogs.map(d => d.dialog);
}
public getDialog(peerID: number, folderID?: number): [Dialog, number] | [] {
const folders: Dialog[][] = [];
if(folderID === undefined) {
const dialogs = this.byFolders;
for(const folderID in dialogs) {
folders.push(dialogs[folderID]);
}
} else {
folders.push(this.getFolder(folderID));
}
for(let folder of folders) {
const index = folder.findIndex(dialog => dialog.peerID == peerID);
if(index !== -1) {
return [folder[index], index];
}
}
return [];
}
/*
var date = Date.now() / 1000 | 0;
var m = date * 0x10000;
var k = (date + 1) * 0x10000;
k - m;
65536
*/
public generateDialogIndex(date?: number) {
if(date === undefined) {
date = tsNow(true) + this.serverTimeManager.serverTimeOffset;
}
return (date * 0x10000) + ((++this.dialogsNum) & 0xFFFF);
}
public generateIndexForDialog(dialog: Dialog, justReturn = false) {
const channelID = this.appPeersManager.isChannel(dialog.peerID) ? -dialog.peerID : 0;
const mid = this.appMessagesIDsManager.getFullMessageID(dialog.top_message, channelID);
const message = this.appMessagesManager.getMessage(mid);
let topDate = (message as Message.message).date || Date.now() / 1000;
if(channelID) {
const channel = this.appChatsManager.getChat(channelID);
if(!topDate || channel.date && channel.date > topDate) {
topDate = channel.date;
}
}
const savedDraft: any = {};// DraftsManager.saveDraft(peerID, dialog.draft); // warning
if(savedDraft && savedDraft.date > topDate) {
topDate = savedDraft.date;
}
if(dialog.pFlags.pinned && !justReturn) {
topDate = this.generateDialogPinnedDate(dialog);
//this.log('topDate', peerID, topDate);
}
const index = this.generateDialogIndex(topDate);
if(justReturn) return index;
dialog.index = index;
}
public generateDialogPinnedDateByIndex(pinnedIndex: number) {
return 0x7fff0000 + (pinnedIndex & 0xFFFF); // 0xFFFF - потому что в папках может быть бесконечное число пиннедов
}
public generateDialogPinnedDate(dialog: Dialog) {
const order = this.pinnedOrders[dialog.folder_id];
const foundIndex = order.indexOf(dialog.peerID);
const pinnedIndex = foundIndex === -1 ? order.push(dialog.peerID) - 1 : foundIndex;
return this.generateDialogPinnedDateByIndex(pinnedIndex);
}
public pushDialog(dialog: Dialog, offsetDate?: number) {
const dialogs = this.getFolder(dialog.folder_id);
const pos = dialogs.findIndex(d => d.peerID == dialog.peerID);
if(pos !== -1) {
dialogs.splice(pos, 1);
}
//if(!this.dialogs[dialog.peerID]) {
this.dialogs[dialog.peerID] = dialog;
//}
if(offsetDate &&
!dialog.pFlags.pinned &&
(!this.dialogsOffsetDate[dialog.folder_id] || offsetDate < this.dialogsOffsetDate[dialog.folder_id])) {
if(pos !== -1) {
// So the dialog jumped to the last position
return false;
}
this.dialogsOffsetDate[dialog.folder_id] = offsetDate;
}
const index = dialog.index;
const len = dialogs.length;
if(!len || index < dialogs[len - 1].index) {
dialogs.push(dialog);
} else if(index >= dialogs[0].index) {
dialogs.unshift(dialog);
} else {
for(let i = 0; i < len; i++) {
if(index > dialogs[i].index) {
dialogs.splice(i, 0, dialog);
break;
}
}
}
}
public dropDialog(peerID: number): [Dialog, number] | [] {
const foundDialog = this.getDialog(peerID);
if(foundDialog[0]) {
this.byFolders[foundDialog[0].folder_id].splice(foundDialog[1], 1);
delete this.dialogs[peerID];
}
return foundDialog;
}
}

262
src/lib/storages/filters.ts

@ -0,0 +1,262 @@
import { copy } from "../../helpers/object";
import type { DialogFilter, Update } from "../../layer";
import type { Modify } from "../../types";
import type { AppPeersManager } from "../appManagers/appPeersManager";
import type { AppUsersManager } from "../appManagers/appUsersManager";
//import type { ApiManagerProxy } from "../mtproto/mtprotoworker";
import type _$rootScope from "../rootScope";
import type {Dialog} from '../appManagers/appMessagesManager';
import apiManager from "../mtproto/mtprotoworker";
export type MyDialogFilter = Modify<DialogFilter, {
pinned_peers: number[],
include_peers: number[],
exclude_peers: number[],
orderIndex?: number
}>;
// ! because 0 index is 'All Chats'
const START_ORDER_INDEX = 1;
export default class FiltersStorage {
public filters: {[filterID: string]: MyDialogFilter} = {};
public orderIndex = START_ORDER_INDEX;
constructor(private appPeersManager: AppPeersManager, private appUsersManager: AppUsersManager, /* private apiManager: ApiManagerProxy, */ private $rootScope: typeof _$rootScope) {
$rootScope.$on('apiUpdate', (e) => {
this.handleUpdate(e.detail);
});
}
public handleUpdate(update: Update) {
switch(update._) {
case 'updateDialogFilter': {
//console.log('updateDialogFilter', update);
if(update.filter) {
this.saveDialogFilter(update.filter as any);
} else if(this.filters[update.id]) { // Папка удалена
//this.getDialogFilters(true);
this.$rootScope.$broadcast('filter_delete', this.filters[update.id]);
delete this.filters[update.id];
}
break;
}
case 'updateDialogFilters': {
//console.warn('updateDialogFilters', update);
const oldFilters = copy(this.filters);
this.getDialogFilters(true).then(filters => {
for(const _filterID in oldFilters) {
const filterID = +_filterID;
if(!filters.find(filter => filter.id == filterID)) { // * deleted
this.handleUpdate({_: 'updateDialogFilter', id: filterID});
}
}
this.handleUpdate({_: 'updateDialogFilterOrder', order: filters.map(filter => filter.id)});
});
break;
}
case 'updateDialogFilterOrder': {
//console.log('updateDialogFilterOrder', update);
this.orderIndex = START_ORDER_INDEX;
update.order.forEach((filterID, idx) => {
const filter = this.filters[filterID];
delete filter.orderIndex;
this.setOrderIndex(filter);
});
this.$rootScope.$broadcast('filter_order', update.order);
break;
}
}
}
public testDialogForFilter(dialog: Dialog, filter: MyDialogFilter) {
// exclude_peers
for(const peerID of filter.exclude_peers) {
if(peerID == dialog.peerID) {
return false;
}
}
// include_peers
for(const peerID of filter.include_peers) {
if(peerID == dialog.peerID) {
return true;
}
}
const pFlags = filter.pFlags;
// exclude_archived
if(pFlags.exclude_archived && dialog.folder_id == 1) {
return false;
}
// exclude_read
if(pFlags.exclude_read && !dialog.unread_count) {
return false;
}
// exclude_muted
if(pFlags.exclude_muted) {
const isMuted = (dialog.notify_settings?.mute_until * 1000) > Date.now();
if(isMuted) {
return false;
}
}
const peerID = dialog.peerID;
if(peerID < 0) {
// broadcasts
if(pFlags.broadcasts && this.appPeersManager.isBroadcast(peerID)) {
return true;
}
// groups
if(pFlags.groups && this.appPeersManager.isAnyGroup(peerID)) {
return true;
}
} else {
// bots
if(this.appPeersManager.isBot(peerID)) {
return !!pFlags.bots;
}
// non_contacts
if(pFlags.non_contacts && !this.appUsersManager.contactsList.has(peerID)) {
return true;
}
// contacts
if(pFlags.contacts && this.appUsersManager.contactsList.has(peerID)) {
return true;
}
}
return false;
}
public toggleDialogPin(peerID: number, filterID: number) {
const filter = this.filters[filterID];
const wasPinned = filter.pinned_peers.findAndSplice(p => p == peerID);
if(!wasPinned) {
filter.pinned_peers.unshift(peerID);
}
return this.updateDialogFilter(filter);
}
public createDialogFilter(filter: MyDialogFilter) {
let maxID = Math.max(1, ...Object.keys(this.filters).map(i => +i));
filter = copy(filter);
filter.id = maxID + 1;
return this.updateDialogFilter(filter);
}
public updateDialogFilter(filter: MyDialogFilter, remove = false) {
const flags = remove ? 0 : 1;
return apiManager.invokeApi('messages.updateDialogFilter', {
flags,
id: filter.id,
filter: remove ? undefined : this.getOutputDialogFilter(filter)
}).then((bool: boolean) => { // возможно нужна проверка и откат, если результат не ТРУ
//console.log('updateDialogFilter bool:', bool);
if(bool) {
/* if(!this.filters[filter.id]) {
this.saveDialogFilter(filter);
}
$rootScope.$broadcast('filter_update', filter); */
this.handleUpdate({
_: 'updateDialogFilter',
id: filter.id,
filter: remove ? undefined : filter as any
});
}
return bool;
});
}
public getOutputDialogFilter(filter: MyDialogFilter) {
const c: MyDialogFilter = copy(filter);
['pinned_peers', 'exclude_peers', 'include_peers'].forEach(key => {
// @ts-ignore
c[key] = c[key].map((peerID: number) => this.appPeersManager.getInputPeerByID(peerID));
});
c.include_peers.forEachReverse((peerID, idx) => {
if(c.pinned_peers.includes(peerID)) {
c.include_peers.splice(idx, 1);
}
});
return c as any as DialogFilter;
}
public async getDialogFilters(overwrite = false): Promise<MyDialogFilter[]> {
const keys = Object.keys(this.filters);
if(keys.length && !overwrite) {
return keys.map(filterID => this.filters[filterID]).sort((a, b) => a.orderIndex - b.orderIndex);
}
const filters: MyDialogFilter[] = await apiManager.invokeApi('messages.getDialogFilters') as any;
for(const filter of filters) {
this.saveDialogFilter(filter, overwrite);
}
//console.log(this.filters);
return filters;
}
public saveDialogFilter(filter: MyDialogFilter, update = true) {
['pinned_peers', 'exclude_peers', 'include_peers'].forEach(key => {
// @ts-ignore
filter[key] = filter[key].map((peer: any) => this.appPeersManager.getPeerID(peer));
});
filter.include_peers.forEachReverse((peerID, idx) => {
if(filter.pinned_peers.includes(peerID)) {
filter.include_peers.splice(idx, 1);
}
});
filter.include_peers = filter.pinned_peers.concat(filter.include_peers);
if(this.filters[filter.id]) {
Object.assign(this.filters[filter.id], filter);
} else {
this.filters[filter.id] = filter;
}
this.setOrderIndex(filter);
if(update) {
this.$rootScope.$broadcast('filter_update', filter);
}
}
public setOrderIndex(filter: MyDialogFilter) {
if(filter.hasOwnProperty('orderIndex')) {
if(filter.orderIndex >= this.orderIndex) {
this.orderIndex = filter.orderIndex + 1;
}
} else {
filter.orderIndex = this.orderIndex++;
}
}
}
Loading…
Cancel
Save