This commit is contained in:
morethanwords 2021-10-21 17:16:43 +04:00
parent 264fbdf7c9
commit 72345caa1e
164 changed files with 5788 additions and 4590 deletions

6
.env
View File

@ -1,5 +1,5 @@
API_ID=1025907
API_HASH=452b0359b988148995f22ff0f4229750
VERSION=0.8.6
VERSION_FULL=0.8.6 (6)
BUILD=6
VERSION=0.9.0
VERSION_FULL=0.9.0 (10)
BUILD=10

View File

@ -1,3 +1,13 @@
### 0.9.0 (10)
* Application can now work with new 64-bit identifiers
* Added Swipe-to-Reply on devices with touchscreen
* Refactored folders & dialogs
* Added confirmation popups for logging out, canceling message editing
* Fixed sending video as file
* Fixed loading document's thumbnail on sending and forwarding
* Fixed inability to open a media sent as file in media viewer
* Other bugfixes and improvements
### 0.8.6
* Added changelogs.
* Audio player improvements: seek, next/previous buttons, volume controls. Changing volume in the video player will affect audio as well.

View File

@ -3,7 +3,7 @@
const { spawn } = require('child_process');
const version = process.argv[2] || 'same';
const changelog = '';
const changelog = process.argv[3] || '';
const child = spawn(`npm`, ['run', 'change-version', version, changelog].filter(Boolean));
child.stdout.on('data', (chunk) => {
console.log(chunk.toString());

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 164 KiB

After

Width:  |  Height:  |  Size: 169 KiB

Binary file not shown.

Binary file not shown.

View File

@ -1 +0,0 @@
<svg width="148" height="256" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><filter x="-9.1%" y="-5%" width="118.2%" height="110%" filterUnits="objectBoundingBox" id="a"><feOffset in="SourceAlpha" result="so1"/><feGaussianBlur stdDeviation="4" in="so1" result="sb1"/><feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.5 0" in="sb1"/></filter><path d="M128.495 111.434L20.685 3.543c-4.73-4.724-12.394-4.724-17.137 0-4.73 4.724-4.73 12.396 0 17.12l99.258 99.331-99.246 99.33c-4.73 4.725-4.73 12.397 0 17.133 4.73 4.724 12.407 4.724 17.137 0l107.81-107.891c4.66-4.675 4.66-12.469-.012-17.132z" id="b"/></defs><g transform="translate(8 8)" fill="none"><use fill="#000" filter="url(#a)" xlink:href="#b"/><use fill="#FFF" xlink:href="#b"/></g></svg>

Before

Width:  |  Height:  |  Size: 792 B

View File

@ -1 +0,0 @@
<svg width="148" height="256" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><filter x="-9.1%" y="-5%" width="118.2%" height="110%" filterUnits="objectBoundingBox" id="a"><feOffset in="SourceAlpha" result="so1"/><feGaussianBlur stdDeviation="4" in="so1" result="sb1"/><feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.5 0" in="sb1"/></filter><path d="M3.504 128.567l107.81 107.891c4.731 4.724 12.395 4.724 17.138 0 4.73-4.724 4.73-12.396 0-17.12l-99.259-99.331 99.247-99.33c4.73-4.725 4.73-12.397 0-17.133-4.73-4.724-12.407-4.724-17.138 0L3.492 111.434c-4.658 4.675-4.658 12.469.012 17.133z" id="b"/></defs><g transform="translate(8 8)" fill="none"><use fill="#000" filter="url(#a)" xlink:href="#b"/><use fill="#FFF" xlink:href="#b"/></g></svg>

Before

Width:  |  Height:  |  Size: 793 B

View File

@ -0,0 +1,8 @@
• Application can now work with new 64-bit identifiers
• Added Swipe-to-Reply on devices with touchscreen
• Refactored folders & dialogs
• Added confirmation popups for logging out, canceling message editing
• Fixed sending video as file
• Fixed loading document's thumbnail on sending and forwarding
• Fixed inability to open a media sent as file in media viewer
• Other bugfixes and improvements

View File

@ -9,6 +9,7 @@ import rootScope from "../lib/rootScope";
import { IS_SAFARI } from "../environment/userAgent";
import { MOUNT_CLASS_TO } from "../config/debug";
import isInDOM from "../helpers/dom/isInDOM";
import { indexOfAndSplice } from "../helpers/array";
export interface AnimationItem {
el: HTMLElement,
@ -101,7 +102,7 @@ export class AnimationIntersector {
}
for(const group in this.byGroups) {
this.byGroups[group].findAndSplice(p => p === player);
indexOfAndSplice(this.byGroups[group], player);
}
this.observer.unobserve(el);

View File

@ -27,7 +27,7 @@ import { onMediaLoad } from "../helpers/files";
// TODO: Safari: попробовать замаскировать подгрузку последнего чанка
// TODO: Safari: пофиксить момент, когда заканчивается песня и пытаешься включить её заново - прогресс сразу в конце
export type MediaItem = {mid: number, peerId: number};
export type MediaItem = {mid: number, peerId: PeerId};
type HTMLMediaElement = HTMLAudioElement | HTMLVideoElement;
@ -47,9 +47,9 @@ export type MediaSearchContext = SearchSuperContext & Partial<{
}>;
type MediaDetails = {
peerId: number,
peerId: PeerId,
mid: number,
docId: string,
docId: DocId,
clean?: boolean,
isScheduled?: boolean,
isSingle?: boolean
@ -57,12 +57,12 @@ type MediaDetails = {
class AppMediaPlaybackController {
private container: HTMLElement;
private media: Map<number, Map<number, HTMLMediaElement>> = new Map();
private media: Map<PeerId, Map<number, HTMLMediaElement>> = new Map();
private scheduled: AppMediaPlaybackController['media'] = new Map();
private mediaDetails: Map<HTMLMediaElement, MediaDetails> = new Map();
private playingMedia: HTMLMediaElement;
private waitingMediaForLoad: Map<number, Map<number, CancellablePromise<void>>> = new Map();
private waitingMediaForLoad: Map<PeerId, Map<number, CancellablePromise<void>>> = new Map();
private waitingScheduledMediaForLoad: AppMediaPlaybackController['waitingMediaForLoad'] = new Map();
private waitingDocumentsForLoad: {[docId: string]: Set<HTMLMediaElement>} = {};
@ -271,7 +271,7 @@ class AppMediaPlaybackController {
return media;
}
public getMedia(peerId: number, mid: number, isScheduled?: boolean) {
public getMedia(peerId: PeerId, mid: number, isScheduled?: boolean) {
const s = (isScheduled ? this.scheduled : this.media).get(peerId);
return s?.get(mid);
}
@ -326,7 +326,7 @@ class AppMediaPlaybackController {
}/* , {once: true} */);
}
public resolveWaitingForLoadMedia(peerId: number, mid: number, isScheduled?: boolean) {
public resolveWaitingForLoadMedia(peerId: PeerId, mid: number, isScheduled?: boolean) {
const w = isScheduled ? this.waitingScheduledMediaForLoad : this.waitingMediaForLoad;
const storage = w.get(peerId);
if(!storage) {

View File

@ -28,7 +28,7 @@ import AppSharedMediaTab from "./sidebarRight/tabs/sharedMedia";
type AppMediaViewerTargetType = {
element: HTMLElement,
mid: number,
peerId: number
peerId: PeerId
};
export default class AppMediaViewer extends AppMediaViewerBase<'caption', 'delete' | 'forward', AppMediaViewerTargetType> {
protected btnMenuDelete: HTMLElement;
@ -157,7 +157,7 @@ export default class AppMediaViewer extends AppMediaViewerBase<'caption', 'delet
return promise;
} */
protected getMessageByPeer(peerId: number, mid: number) {
protected getMessageByPeer(peerId: PeerId, mid: number) {
return this.searchContext.isScheduled ? appMessagesManager.getScheduledMessageByPeer(peerId, mid) : appMessagesManager.getMessageByPeer(peerId, mid);
}
@ -193,6 +193,7 @@ export default class AppMediaViewer extends AppMediaViewerBase<'caption', 'delet
const {mid, peerId} = this.target;
if(mid && mid !== Number.MAX_SAFE_INTEGER) {
const threadId = this.searchContext.threadId;
const message = this.getMessageByPeer(peerId, mid);
this.close(e)
//.then(() => mediaSizes.isMobile ? appSidebarRight.sharedMediaTab.closeBtn.click() : Promise.resolve())
.then(() => {
@ -203,7 +204,6 @@ export default class AppMediaViewer extends AppMediaViewerBase<'caption', 'delet
}
}
const message = this.getMessageByPeer(peerId, mid);
appImManager.setInnerPeer(message.peerId, mid, threadId ? 'discussion' : undefined, threadId);
});
}

View File

@ -5,15 +5,16 @@
*/
import AvatarListLoader from "../helpers/avatarListLoader";
import { Photo } from "../layer";
import appImManager from "../lib/appManagers/appImManager";
import appPhotosManager from "../lib/appManagers/appPhotosManager";
import AppMediaViewerBase from "./appMediaViewerBase";
type AppMediaViewerAvatarTargetType = {element: HTMLElement, photoId: string};
type AppMediaViewerAvatarTargetType = {element: HTMLElement, photoId: Photo.photo['id']};
export default class AppMediaViewerAvatar extends AppMediaViewerBase<'', 'delete', AppMediaViewerAvatarTargetType> {
public peerId: number;
public peerId: PeerId;
constructor(peerId: number) {
constructor(peerId: PeerId) {
super(new AvatarListLoader({peerId}), [/* 'delete' */]);
this.peerId = peerId;
@ -45,7 +46,7 @@ export default class AppMediaViewerAvatar extends AppMediaViewerBase<'', 'delete
appPhotosManager.savePhotoFile(appPhotosManager.getPhoto(this.target.photoId), appImManager.chat.bubbles.lazyLoadQueue.queueId);
};
public async openMedia(photoId: string, target?: HTMLElement, fromRight = 0, prevTargets?: AppMediaViewerAvatarTargetType[], nextTargets?: AppMediaViewerAvatarTargetType[]) {
public async openMedia(photoId: Photo.photo['id'], target?: HTMLElement, fromRight = 0, prevTargets?: AppMediaViewerAvatarTargetType[], nextTargets?: AppMediaViewerAvatarTargetType[]) {
if(this.setMoverPromise) return this.setMoverPromise;
const photo = appPhotosManager.getPhoto(photoId);

View File

@ -1096,7 +1096,7 @@ export default class AppMediaViewerBase<
} */
}
protected setAuthorInfo(fromId: number, timestamp: number) {
protected setAuthorInfo(fromId: PeerId, timestamp: number) {
replaceContent(this.author.date, formatFullSentTime(timestamp));
replaceContent(this.author.nameEl, new PeerTitle({
@ -1115,7 +1115,7 @@ export default class AppMediaViewerBase<
protected async _openMedia(
media: MyDocument | MyPhoto,
timestamp: number,
fromId: number,
fromId: PeerId,
fromRight: number,
target?: HTMLElement,
reverse = false,

View File

@ -1080,7 +1080,7 @@
// type AppMediaViewerTargetType = {
// element: HTMLElement,
// mid: number,
// peerId: number
// peerId: PeerId
// };
// export default class AppMediaViewer extends AppMediaViewerBase<'caption', 'delete' | 'forward', AppMediaViewerTargetType> {
// public currentMessageId = 0;
@ -1336,9 +1336,9 @@
// type AppMediaViewerAvatarTargetType = {element: HTMLElement, photoId: string};
// export class AppMediaViewerAvatar extends AppMediaViewerBase<'', 'delete', AppMediaViewerAvatarTargetType> {
// public currentPhotoId: string;
// public peerId: number;
// public peerId: PeerId;
// constructor(peerId: number) {
// constructor(peerId: PeerId) {
// super(['delete']);
// this.peerId = peerId;
@ -1373,7 +1373,7 @@
// };
// protected loadMoreMedia = (older = true) => {
// if(this.peerId < 0) return Promise.resolve(); // ! это значит, что открыло аватар чата, но следующих фотографий нет.
// if(this.peerId.isAnyChat()) return Promise.resolve(); // ! это значит, что открыло аватар чата, но следующих фотографий нет.
// if(this.loadedAllMediaDown) return Promise.resolve();
// if(this.loadMediaPromiseDown) return this.loadMediaPromiseDown;

View File

@ -10,6 +10,8 @@ import { logger } from "../lib/logger";
import { doubleRaf } from "../helpers/schedulers";
import blurActiveElement from "../helpers/dom/blurActiveElement";
import { cancelEvent } from "../helpers/dom/cancelEvent";
import { indexOfAndSplice } from "../helpers/array";
import isSwipingBackSafari from "../helpers/dom/isSwipingBackSafari";
export type NavigationItem = {
type: 'left' | 'right' | 'im' | 'chat' | 'popup' | 'media' | 'menu' |
@ -73,7 +75,17 @@ export class AppNavigationController {
if(e.touches.length > 1) return;
this.debug && this.log('touchstart');
const detach = () => {
if(isSwipingBackSafari(e)) {
isPossibleSwipe = true;
window.addEventListener('touchend', () => {
setTimeout(() => {
isPossibleSwipe = false;
}, 100);
}, {passive: true, once: true});
}
/* const detach = () => {
window.removeEventListener('touchend', onTouchEnd);
window.removeEventListener('touchmove', onTouchMove);
};
@ -105,7 +117,7 @@ export class AppNavigationController {
};
window.addEventListener('touchend', onTouchEnd, options);
window.addEventListener('touchmove', onTouchMove, options);
window.addEventListener('touchmove', onTouchMove, options); */
}, options);
}
@ -171,7 +183,7 @@ export class AppNavigationController {
}
public removeItem(item: NavigationItem) {
this.navigations.findAndSplice(i => i === item);
indexOfAndSplice(this.navigations, item);
}
public removeByType(type: NavigationItem['type'], single = false) {

View File

@ -74,7 +74,7 @@ export default class AppSearch {
private listsContainer: HTMLDivElement = null;
private peerId = 0; // 0 - means global
private peerId: PeerId; // 0 - means global
private threadId = 0;
private scrollable: Scrollable;
@ -117,7 +117,7 @@ export default class AppSearch {
if(all) {
this.searchInput.value = '';
this.query = '';
this.peerId = 0;
this.peerId = undefined;
this.threadId = 0;
}
@ -132,7 +132,7 @@ export default class AppSearch {
this.searchPromise = null;
}
public beginSearch(peerId = 0, threadId = 0, query = '') {
public beginSearch(peerId?: PeerId, threadId = 0, query = '') {
this.peerId = peerId;
this.threadId = threadId;

View File

@ -50,12 +50,13 @@ import { cancelEvent } from "../helpers/dom/cancelEvent";
import { attachClickEvent, simulateClickEvent } from "../helpers/dom/clickEvent";
import { MyDocument } from "../lib/appManagers/appDocsManager";
import AppMediaViewer from "./appMediaViewer";
import lockTouchScroll from "../helpers/dom/lockTouchScroll";
//const testScroll = false;
export type SearchSuperType = MyInputMessagesFilter/* | 'members' */;
export type SearchSuperContext = {
peerId: number,
peerId: PeerId,
inputFilter: {_: MyInputMessagesFilter},
query?: string,
maxId?: number,
@ -81,7 +82,7 @@ class SearchContextMenu {
private buttons: (ButtonMenuItemOptions & {verify?: () => boolean, withSelection?: true})[];
private element: HTMLElement;
private target: HTMLElement;
private peerId: number;
private peerId: PeerId;
private mid: number;
private isSelected: boolean;
@ -109,7 +110,7 @@ class SearchContextMenu {
if(e instanceof MouseEvent) e.cancelBubble = true;
this.target = item;
this.peerId = +item.dataset.peerId;
this.peerId = item.dataset.peerId.toPeerId();
this.mid = +item.dataset.mid;
this.isSelected = searchSuper.selection.isMidSelected(this.peerId, this.mid);
@ -236,7 +237,7 @@ export default class AppSearchSuper {
private lazyLoadQueue = new LazyLoadQueue();
public middleware = getMiddleware();
public historyStorage: Partial<{[type in SearchSuperType]: {mid: number, peerId: number}[]}> = {};
public historyStorage: Partial<{[type in SearchSuperType]: {mid: number, peerId: PeerId}[]}> = {};
public usedFromHistory: Partial<{[type in SearchSuperType]: number}> = {};
public urlsToRevoke: string[] = [];
@ -326,10 +327,35 @@ export default class AppSearchSuper {
this.tabsContainer = document.createElement('div');
this.tabsContainer.classList.add('search-super-tabs-container', 'tabs-container');
let unlockScroll: ReturnType<typeof lockTouchScroll>;
if(IS_TOUCH_SUPPORTED) {
handleTabSwipe(this.tabsContainer, (next) => {
const prevId = this.selectTab.prevId();
this.selectTab(next ? prevId + 1 : prevId - 1);
handleTabSwipe({
element: this.tabsContainer,
onSwipe: (xDiff, yDiff, e) => {
const prevId = this.selectTab.prevId();
const children = Array.from(this.tabsMenu.children) as HTMLElement[];
let idx: number;
if(xDiff > 0) {
for(let i = prevId + 1; i < children.length; ++i) {
if(!children[i].classList.contains('hide')) {
idx = i;
break;
}
}
} else {
for(let i = prevId - 1; i >= 0; --i) {
if(!children[i].classList.contains('hide')) {
idx = i;
break;
}
}
}
if(idx !== undefined) {
unlockScroll = lockTouchScroll(this.tabsContainer);
this.selectTab(idx);
}
}
});
}
@ -441,6 +467,11 @@ export default class AppSearchSuper {
this.scrollable.scrollTop = this.mediaTab.scroll.scrollTop;
}
if(unlockScroll) {
unlockScroll();
unlockScroll = undefined;
}
this.onTransitionEnd();
}, undefined, navScrollable);
@ -461,14 +492,14 @@ export default class AppSearchSuper {
return;
}
const peerId = +target.dataset.peerId;
const peerId = target.dataset.peerId.toPeerId();
const targets = (Array.from(this.tabs[inputFilter].querySelectorAll('.' + targetClassName)) as HTMLElement[]).map(el => {
const containerEl = findUpClassName(el, className);
return {
element: el,
mid: +containerEl.dataset.mid,
peerId: +containerEl.dataset.peerId
peerId: containerEl.dataset.peerId.toPeerId()
};
});
@ -923,7 +954,7 @@ export default class AppSearchSuper {
}
private loadChats() {
const renderedPeerIds: Set<number> = new Set();
const renderedPeerIds: Set<PeerId> = new Set();
const middleware = this.middleware.get();
for(let i in this.searchGroups) {
@ -934,7 +965,7 @@ export default class AppSearchSuper {
const query = this.searchContext.query;
if(query) {
const setResults = (results: number[], group: SearchGroup, showMembersCount = false) => {
const setResults = (results: PeerId[], group: SearchGroup, showMembersCount = false) => {
results.forEach((peerId) => {
if(renderedPeerIds.has(peerId)) {
return;
@ -957,7 +988,7 @@ export default class AppSearchSuper {
if(showMembersCount && (peer.participants_count || peer.participants)) {
const regExp = new RegExp(`(${escapeRegExp(query)}|${escapeRegExp(cleanSearchText(query))})`, 'gi');
dom.titleSpan.innerHTML = dom.titleSpan.innerHTML.replace(regExp, '<i>$1</i>');
dom.lastMessageSpan.append(appProfileManager.getChatMembersString(-peerId));
dom.lastMessageSpan.append(appProfileManager.getChatMembersString(peerId.toChatId()));
} else if(peerId === rootScope.myId) {
dom.lastMessageSpan.append(i18n('Presence.YourChat'));
} else {
@ -989,7 +1020,7 @@ export default class AppSearchSuper {
};
return Promise.all([
appUsersManager.getContacts(query, true)
appUsersManager.getContactsPeerIds(query, true)
.then(onLoad)
.then((contacts) => {
if(contacts) {
@ -1050,7 +1081,7 @@ export default class AppSearchSuper {
autonomous: true
});
dom.lastMessageSpan.append(peerId > 0 ? appUsersManager.getUserStatusString(peerId) : appProfileManager.getChatMembersString(-peerId));
dom.lastMessageSpan.append(peerId.isUser() ? appUsersManager.getUserStatusString(peerId) : appProfileManager.getChatMembersString(peerId.toChatId()));
});
if(!state.recentSearch.length) {
@ -1093,7 +1124,7 @@ export default class AppSearchSuper {
}
private loadMembers(mediaTab: SearchSuperMediaTab) {
const id = -this.searchContext.peerId;
const id = this.searchContext.peerId.toChatId();
const middleware = this.middleware.get();
let promise: Promise<void>;
@ -1114,7 +1145,7 @@ export default class AppSearchSuper {
return;
}
const peerId = +li.dataset.peerId;
const peerId = li.dataset.peerId.toPeerId();
let promise: Promise<any> = Promise.resolve();
if(mediaSizes.isMobile) {
promise = appSidebarRight.toggleSidebar(false);
@ -1130,7 +1161,7 @@ export default class AppSearchSuper {
participants.forEach(participant => {
const peerId = appChatsManager.getParticipantPeerId(participant);
if(peerId < 0) {
if(peerId.isAnyChat()) {
return;
}
@ -1386,7 +1417,7 @@ export default class AppSearchSuper {
return !this.loaded[inputFilter] || (this.historyStorage[inputFilter] && this.usedFromHistory[inputFilter] < this.historyStorage[inputFilter].length);
});
if(peerId > 0) {
if(peerId.isUser()) {
toLoad.findAndSplice(mediaTab => mediaTab.type === 'members');
}
@ -1456,7 +1487,7 @@ export default class AppSearchSuper {
}
public canViewMembers() {
return this.searchContext.peerId < 0 && !appChatsManager.isBroadcast(-this.searchContext.peerId) && appChatsManager.hasRights(-this.searchContext.peerId, 'view_participants');
return this.searchContext.peerId.isAnyChat() && !appChatsManager.isBroadcast(this.searchContext.peerId.toChatId()) && appChatsManager.hasRights(this.searchContext.peerId.toChatId(), 'view_participants');
}
public cleanup() {
@ -1566,7 +1597,7 @@ export default class AppSearchSuper {
}
public setQuery({peerId, query, threadId, historyStorage, folderId, minDate, maxDate}: {
peerId: number,
peerId: PeerId,
query?: string,
threadId?: number,
historyStorage?: AppSearchSuper['historyStorage'],
@ -1575,7 +1606,7 @@ export default class AppSearchSuper {
maxDate?: number
}) {
this.searchContext = {
peerId: peerId || 0,
peerId,
query: query || '',
inputFilter: {_: this.mediaTab.inputFilter},
threadId,

View File

@ -20,7 +20,7 @@ import findUpClassName from "../helpers/dom/findUpClassName";
import PeerTitle from "./peerTitle";
import { cancelEvent } from "../helpers/dom/cancelEvent";
import replaceContent from "../helpers/dom/replaceContent";
import { filterUnique } from "../helpers/array";
import { filterUnique, indexOfAndSplice } from "../helpers/array";
import debounce from "../helpers/schedulers/debounce";
import windowSize from "../helpers/windowSize";
@ -28,7 +28,6 @@ type PeerType = 'contacts' | 'dialogs' | 'channelParticipants';
// TODO: правильная сортировка для addMembers, т.е. для peerType: 'contacts', потому что там идут сначала контакты - потом неконтакты, а должно всё сортироваться по имени
let loadedAllDialogs = false, loadAllDialogsPromise: Promise<any>;
export default class AppSelectPeers {
public container = document.createElement('div');
public list = appDialogsManager.createChatList(/* {
@ -42,8 +41,8 @@ export default class AppSelectPeers {
public selectedContainer: HTMLElement;
public input: HTMLInputElement;
//public selected: {[peerId: number]: HTMLElement} = {};
public selected = new Set<any>();
//public selected: {[peerId: PeerId]: HTMLElement} = {};
public selected = new Set<PeerId | string>();
public freezed = false;
@ -52,23 +51,23 @@ export default class AppSelectPeers {
private promise: Promise<any>;
private query = '';
private cachedContacts: number[];
private cachedContacts: PeerId[];
private loadedWhat: Partial<{[k in 'dialogs' | 'archived' | 'contacts' | 'channelParticipants']: true}> = {};
private renderedPeerIds: Set<number> = new Set();
private renderedPeerIds: Set<PeerId> = new Set();
private appendTo: HTMLElement;
private onChange: (length: number) => void;
private peerType: PeerType[] = ['dialogs'];
private renderResultsFunc: (peerIds: number[]) => void;
private renderResultsFunc: (peerIds: PeerId[]) => void;
private chatRightsAction: ChatRights;
private multiSelect = true;
private rippleEnabled = true;
private avatarSize = 48;
private tempIds: {[k in keyof AppSelectPeers['loadedWhat']]: number} = {};
private peerId = 0;
private peerId: PeerId;
private placeholder: LangPackKey;
@ -95,7 +94,7 @@ export default class AppSelectPeers {
this.container.classList.add('selector');
const f = (this.renderResultsFunc || this.renderResults).bind(this);
this.renderResultsFunc = (peerIds: number[]) => {
this.renderResultsFunc = (peerIds: PeerId[]) => {
if(this.needSwitchList) {
this.scrollable.splitUp.replaceWith(this.list);
this.scrollable.setVirtualContainer(this.list);
@ -144,7 +143,7 @@ export default class AppSelectPeers {
const peerId = target.dataset.key;
const li = this.chatsContainer.querySelector('[data-peer-id="' + peerId + '"]') as HTMLElement;
if(!li) {
this.remove(+peerId || peerId);
this.remove(peerId.toPeerId());
} else {
li.click();
}
@ -165,8 +164,8 @@ export default class AppSelectPeers {
if(!target) return;
if(this.freezed) return;
let key: any = target.dataset.peerId;
key = +key || key;
let key: PeerId | string = target.dataset.peerId;
key = key.isPeerId() ? key.toPeerId() : key;
if(!this.multiSelect) {
this.add(key);
@ -208,7 +207,7 @@ export default class AppSelectPeers {
private onInput = () => {
const value = this.input.value;
if(this.query !== value) {
if(this.peerType.includes('contacts')) {
if(this.peerType.includes('contacts') || this.peerType.includes('dialogs')) {
this.cachedContacts = null;
}
@ -283,9 +282,9 @@ export default class AppSelectPeers {
this.renderSaved();
this.offsetIndex = newOffsetIndex;
this.renderResultsFunc(dialogs.map(dialog => dialog.peerId));
}
this.renderResultsFunc(dialogs.map(dialog => dialog.peerId));
if(value.isEnd) {
if(!this.loadedWhat.dialogs) {
@ -299,18 +298,18 @@ export default class AppSelectPeers {
} else {
this.loadedWhat.archived = true;
if(!this.loadedWhat.contacts && this.peerType.includes('contacts')) {
if(!this.loadedWhat.contacts/* && this.peerType.includes('contacts') */) {
return this.getMoreContacts();
}
}
}
}
private filterByRights(peerId: number) {
private filterByRights(peerId: PeerId) {
return (
peerId > 0 &&
peerId.isUser() &&
(this.chatRightsAction !== 'send_messages' || appUsersManager.canSendToUser(peerId))
) || appChatsManager.hasRights(-peerId, this.chatRightsAction);
) || appChatsManager.hasRights(peerId.toChatId(), this.chatRightsAction);
}
private async getMoreContacts() {
@ -320,6 +319,8 @@ export default class AppSelectPeers {
return;
}
const isGlobalSearch = this.peerType.includes('contacts');
if(!this.cachedContacts) {
/* const promises: Promise<any>[] = [appUsersManager.getContacts(this.query)];
if(!this.peerType.includes('dialogs')) {
@ -330,39 +331,40 @@ export default class AppSelectPeers {
this.cachedContacts = (await this.promise)[0].slice(); */
const tempId = this.getTempId('contacts');
const promise = Promise.all([
appUsersManager.getContacts(this.query),
isGlobalSearch ? appUsersManager.getContactsPeerIds(this.query) : [],
this.query ? appUsersManager.searchContacts(this.query) : undefined
]);
this.promise = promise;
const [cachedContacts, searchResult] = await promise;
let [cachedContacts, searchResult] = await promise;
if(this.tempIds.contacts !== tempId) {
return;
}
if(searchResult) {
let resultPeerIds = searchResult.my_results.concat(searchResult.results);
// do not add global result if only dialogs needed
let resultPeerIds = isGlobalSearch ? searchResult.my_results.concat(searchResult.results) : searchResult.my_results;
if(this.chatRightsAction) {
resultPeerIds = resultPeerIds.filter(peerId => this.filterByRights(peerId));
}
if(!this.peerType.includes('dialogs')) {
resultPeerIds = resultPeerIds.filter(peerId => peerId > 0);
resultPeerIds = resultPeerIds.filter(peerId => peerId.isUser());
}
this.cachedContacts = filterUnique(cachedContacts.concat(resultPeerIds));
} else this.cachedContacts = cachedContacts.slice();
this.cachedContacts.findAndSplice(userId => userId === rootScope.myId); // no my account
indexOfAndSplice(this.cachedContacts, rootScope.myId); // no my account
this.promise = null;
}
if(this.cachedContacts.length) {
// if(this.cachedContacts.length) {
const pageCount = windowSize.windowH / 72 * 1.25 | 0;
const arr = this.cachedContacts.splice(0, pageCount);
this.renderResultsFunc(arr);
}
// }
if(!this.cachedContacts.length) {
this.loadedWhat.contacts = true;
@ -384,7 +386,7 @@ export default class AppSelectPeers {
const pageCount = 50; // same as in group permissions to use cache
const tempId = this.getTempId('channelParticipants');
const promise = appProfileManager.getChannelParticipants(-this.peerId, {_: 'channelParticipantsSearch', q: this.query}, pageCount, this.list.childElementCount);
const promise = appProfileManager.getChannelParticipants(this.peerId.toChatId(), {_: 'channelParticipantsSearch', q: this.query}, pageCount, this.list.childElementCount);
const participants = await promise;
if(this.tempIds.channelParticipants !== tempId) {
return;
@ -393,7 +395,7 @@ export default class AppSelectPeers {
const peerIds = participants.participants.map(participant => {
return appChatsManager.getParticipantPeerId(participant);
});
peerIds.findAndSplice(u => u === rootScope.myId);
indexOfAndSplice(peerIds, rootScope.myId);
this.renderResultsFunc(peerIds);
if(this.list.childElementCount >= participants.count || participants.participants.length < pageCount) {
@ -409,18 +411,18 @@ export default class AppSelectPeers {
const get = () => {
const promises: Promise<any>[] = [];
if(!loadedAllDialogs && (this.peerType.includes('dialogs')/* || this.peerType.includes('contacts') */)) {
if(!loadAllDialogsPromise) {
loadAllDialogsPromise = appMessagesManager.getConversationsAll()
.then(() => {
loadedAllDialogs = true;
}).finally(() => {
loadAllDialogsPromise = null;
});
}
// if(!loadedAllDialogs && (this.peerType.includes('dialogs')/* || this.peerType.includes('contacts') */)) {
// if(!loadAllDialogsPromise) {
// loadAllDialogsPromise = appMessagesManager.getConversationsAll()
// .then(() => {
// loadedAllDialogs = true;
// }).finally(() => {
// loadAllDialogsPromise = null;
// });
// }
promises.push(loadAllDialogsPromise);
}
// promises.push(loadAllDialogsPromise);
// }
if((this.peerType.includes('dialogs')/* || this.loadedWhat.contacts */) && !this.loadedWhat.archived) { // to load non-contacts
promises.push(this.getMoreDialogs());
@ -430,7 +432,7 @@ export default class AppSelectPeers {
}
}
if(this.peerType.includes('contacts') && !this.loadedWhat.contacts) {
if((this.peerType.includes('contacts') || this.peerType.includes('dialogs')) && !this.loadedWhat.contacts) {
promises.push(this.getMoreContacts());
}
@ -450,7 +452,7 @@ export default class AppSelectPeers {
return promise;
}
private renderResults(peerIds: number[]) {
private renderResults(peerIds: PeerId[]) {
//console.log('will renderResults:', peerIds);
// оставим только неконтакты с диалогов
@ -482,8 +484,8 @@ export default class AppSelectPeers {
}
let subtitleEl: HTMLElement;
if(peerId < 0) {
subtitleEl = appProfileManager.getChatMembersString(-peerId);
if(peerId.isAnyChat()) {
subtitleEl = appProfileManager.getChatMembersString(peerId.toChatId());
} else if(peerId === rootScope.myId) {
subtitleEl = i18n(this.selfPresence);
} else {
@ -494,9 +496,9 @@ export default class AppSelectPeers {
});
}
public add(peerId: any, title?: string | HTMLElement, scroll = true) {
public add(key: PeerId | string, title?: string | HTMLElement, scroll = true) {
//console.trace('add');
this.selected.add(peerId);
this.selected.add(key);
if(!this.multiSelect) {
this.onChange(this.selected.size);
@ -516,13 +518,13 @@ export default class AppSelectPeers {
avatarEl.setAttribute('dialog', '1');
avatarEl.classList.add('avatar-32');
div.dataset.key = '' + peerId;
if(typeof(peerId) === 'number') {
div.dataset.key = '' + key;
if(key.isPeerId()) {
if(title === undefined) {
title = new PeerTitle({peerId, dialog: true}).element;
title = new PeerTitle({peerId: key.toPeerId(), dialog: true}).element;
}
avatarEl.setAttribute('peer', '' + peerId);
avatarEl.setAttribute('peer', '' + key);
}
if(title) {
@ -547,7 +549,7 @@ export default class AppSelectPeers {
return div;
}
public remove(key: any) {
public remove(key: PeerId | string) {
if(!this.multiSelect) return;
//const div = this.selected[peerId];
const div = this.selectedContainer.querySelector(`[data-key="${key}"]`) as HTMLElement;

View File

@ -352,7 +352,7 @@ export const findAudioTargets = (anchor: HTMLElement, useSearch: boolean) => {
const elements = Array.from(container.querySelectorAll(selector)) as HTMLElement[];
const idx = elements.indexOf(anchor);
const mediaItems: MediaItem[] = elements.map(element => ({peerId: +element.dataset.peerId, mid: +element.dataset.mid}));
const mediaItems: MediaItem[] = elements.map(element => ({peerId: element.dataset.peerId.toPeerId(), mid: +element.dataset.mid}));
prev = mediaItems.slice(0, idx);
next = mediaItems.slice(idx + 1);
@ -481,31 +481,31 @@ export default class AudioElement extends HTMLElement {
return togglePlay;
};
if(doc.thumbs?.length) {
const imgs: HTMLImageElement[] = [];
const wrapped = wrapPhoto({
photo: doc,
message: null,
container: toggle,
boxWidth: 48,
boxHeight: 48,
loadPromises: this.loadPromises,
withoutPreloader: true,
lazyLoadQueue: this.lazyLoadQueue
});
toggle.style.width = toggle.style.height = '';
if(wrapped.images.thumb) imgs.push(wrapped.images.thumb);
if(wrapped.images.full) imgs.push(wrapped.images.full);
this.classList.add('audio-with-thumb');
imgs.forEach(img => img.classList.add('audio-thumb'));
}
if(!isOutgoing) {
let preloader: ProgressivePreloader = this.preloader;
onLoad(doc.type !== 'audio' && !this.noAutoDownload);
if(doc.thumbs) {
const imgs: HTMLImageElement[] = [];
const wrapped = wrapPhoto({
photo: doc,
message: null,
container: toggle,
boxWidth: 48,
boxHeight: 48,
loadPromises: this.loadPromises,
withoutPreloader: true,
lazyLoadQueue: this.lazyLoadQueue
});
toggle.style.width = toggle.style.height = '';
if(wrapped.images.thumb) imgs.push(wrapped.images.thumb);
if(wrapped.images.full) imgs.push(wrapped.images.full);
this.classList.add('audio-with-thumb');
imgs.forEach(img => img.classList.add('audio-thumb'));
}
const r = (shouldPlay: boolean) => {
if(this.audio.src) {
return;

View File

@ -7,7 +7,7 @@
import appMessagesManager from "../lib/appManagers/appMessagesManager";
import appProfileManager from "../lib/appManagers/appProfileManager";
import rootScope from "../lib/rootScope";
import { Message } from "../layer";
import { Message, Photo } from "../layer";
import appPeersManager from "../lib/appManagers/appPeersManager";
import appPhotosManager from "../lib/appManagers/appPhotosManager";
import type { LazyLoadQueueIntersector } from "./lazyLoadQueue";
@ -16,8 +16,9 @@ import { cancelEvent } from "../helpers/dom/cancelEvent";
import appAvatarsManager from "../lib/appManagers/appAvatarsManager";
import AppMediaViewer from "./appMediaViewer";
import AppMediaViewerAvatar from "./appMediaViewerAvatar";
import { NULL_PEER_ID } from "../lib/mtproto/mtproto_config";
const onAvatarUpdate = (peerId: number) => {
const onAvatarUpdate = (peerId: PeerId) => {
appAvatarsManager.removeFromAvatarsCache(peerId);
(Array.from(document.querySelectorAll('avatar-element[peer="' + peerId + '"]')) as AvatarElement[]).forEach(elem => {
//console.log('updating avatar:', elem);
@ -32,7 +33,14 @@ rootScope.addEventListener('peer_title_edit', (peerId) => {
}
});
export async function openAvatarViewer(target: HTMLElement, peerId: number, middleware: () => boolean, message?: any, prevTargets?: {element: HTMLElement, item: string | Message.messageService}[], nextTargets?: typeof prevTargets) {
export async function openAvatarViewer(
target: HTMLElement,
peerId: PeerId,
middleware: () => boolean,
message?: any,
prevTargets?: {element: HTMLElement, item: Photo.photo['id'] | Message.messageService}[],
nextTargets?: typeof prevTargets
) {
let photo = await appProfileManager.getFullPhoto(peerId);
if(!middleware() || !photo) {
return;
@ -43,7 +51,7 @@ export async function openAvatarViewer(target: HTMLElement, peerId: number, midd
return good ? target : null;
};
if(peerId < 0) {
if(peerId.isAnyChat()) {
const hadMessage = !!message;
const inputFilter = 'inputMessagesFilterChatPhotos';
if(!message) {
@ -105,11 +113,11 @@ export async function openAvatarViewer(target: HTMLElement, peerId: number, midd
}
}
const believeMe: Map<number, Set<AvatarElement>> = new Map();
const seen: Set<number> = new Set();
const believeMe: Map<PeerId, Set<AvatarElement>> = new Map();
const seen: Set<PeerId> = new Set();
export default class AvatarElement extends HTMLElement {
private peerId: number;
private peerId: PeerId;
private isDialog = false;
private peerTitle: string;
public loadPromises: Promise<any>[];
@ -160,13 +168,14 @@ export default class AvatarElement extends HTMLElement {
//console.log('avatar changed attribute:', name, oldValue, newValue);
// вызывается при изменении одного из перечисленных выше атрибутов
if(name === 'peer') {
if(this.peerId === +newValue) {
const newPeerId = (newValue || '').toPeerId() || NULL_PEER_ID;
if(this.peerId === newPeerId) {
return;
}
this.peerId = appPeersManager.getPeerMigratedTo(+newValue) || +newValue;
this.peerId = appPeersManager.getPeerMigratedTo(newPeerId) || newPeerId;
const wasPeerId = +oldValue;
const wasPeerId = (oldValue || '').toPeerId() || NULL_PEER_ID;
if(wasPeerId) {
const set = believeMe.get(wasPeerId);
if(set) {

View File

@ -42,7 +42,7 @@ export default class AutocompletePeerHelper extends AutocompleteHelper {
});
}
public render(data: {peerId: number, name?: string, description?: string}[]) {
public render(data: {peerId: PeerId, name?: string, description?: string}[]) {
if(this.init) {
if(!data.length) {
return;
@ -71,7 +71,7 @@ export default class AutocompletePeerHelper extends AutocompleteHelper {
public static listElement(options: {
className: string,
peerId: number,
peerId: PeerId,
name?: string,
description?: string
}) {

View File

@ -8,9 +8,10 @@ import rootScope from "../../lib/rootScope";
//import { generatePathData } from "../../helpers/dom";
import { MyMessage } from "../../lib/appManagers/appMessagesManager";
import type Chat from "./chat";
import { indexOfAndSplice } from "../../helpers/array";
type Group = {bubble: HTMLElement, mid: number, timestamp: number}[];
type BubbleGroup = {timestamp: number, fromId: number, mid: number, group: Group};
type BubbleGroup = {timestamp: number, fromId: PeerId, mid: number, group: Group};
export default class BubbleGroups {
private bubbles: Array<BubbleGroup> = []; // map to group
private detailsMap: Map<HTMLElement, BubbleGroup> = new Map();
@ -28,7 +29,7 @@ export default class BubbleGroups {
if(details.group.length) {
details.group.findAndSplice(d => d.bubble === bubble);
if(!details.group.length) {
this.groups.findAndSplice(g => g === details.group);
indexOfAndSplice(this.groups, details.group);
} else {
this.updateGroup(details.group);
}
@ -37,6 +38,13 @@ export default class BubbleGroups {
this.detailsMap.delete(bubble);
}
}
changeBubbleMid(bubble: HTMLElement, mid: number) {
const details = this.detailsMap.get(bubble);
if(details) {
details.mid = mid;
}
}
addBubble(bubble: HTMLElement, message: MyMessage, reverse: boolean) {
//return;
@ -48,7 +56,7 @@ export default class BubbleGroups {
// fix for saved messages forward to self
if(fromId === rootScope.myId && message.peerId === rootScope.myId && (message as any).fwdFromId === fromId) {
fromId = -fromId;
fromId = fromId.toPeerId(true);
}
// try to find added

View File

@ -20,7 +20,7 @@ import { CHAT_ANIMATION_GROUP } from "../../lib/appManagers/appImManager";
import { getObjectKeysAndSort } from "../../helpers/object";
import { IS_TOUCH_SUPPORTED } from "../../environment/touchSupport";
import { logger } from "../../lib/logger";
import rootScope, { BroadcastEvents } from "../../lib/rootScope";
import rootScope from "../../lib/rootScope";
import BubbleGroups from "./bubbleGroups";
import PopupDatePicker from "../popups/datePicker";
import PopupForward from "../popups/forward";
@ -75,6 +75,9 @@ import { SEND_WHEN_ONLINE_TIMESTAMP } from "../../lib/mtproto/constants";
import windowSize from "../../helpers/windowSize";
import { formatPhoneNumber } from "../../helpers/formatPhoneNumber";
import AppMediaViewer from "../appMediaViewer";
import SetTransition from "../singleTransition";
import handleHorizontalSwipe from "../../helpers/dom/handleHorizontalSwipe";
import { cancelContextMenuOpening } from "../misc";
const USE_MEDIA_TAILS = false;
const IGNORE_ACTIONS: Set<Message.messageService['action']['_']> = new Set([
@ -98,11 +101,11 @@ export default class ChatBubbles {
private getHistoryTopPromise: Promise<boolean>;
private getHistoryBottomPromise: Promise<boolean>;
public peerId = 0;
public peerId: PeerId;
//public messagesCount: number = -1;
private unreadOut = new Set<number>();
public needUpdate: {replyToPeerId: number, replyMid: number, mid: number}[] = []; // if need wrapSingleMessage
public needUpdate: {replyToPeerId: PeerId, replyMid: number, mid: number}[] = []; // if need wrapSingleMessage
public bubbles: {[mid: string]: HTMLDivElement} = {};
public skippedMids: Set<number> = new Set();
@ -170,7 +173,8 @@ export default class ChatBubbles {
private viewsMids: Set<number> = new Set();
private sendViewCountersDebounced: () => Promise<void>;
constructor(private chat: Chat,
constructor(
private chat: Chat,
private appMessagesManager: AppMessagesManager,
private appStickersManager: AppStickersManager,
private appUsersManager: AppUsersManager,
@ -216,7 +220,7 @@ export default class ChatBubbles {
const message = this.chat.getMessage(mid);
if(+bubble.dataset.timestamp >= (message.date + serverTimeManager.serverTimeOffset - 1)) {
//this.bubbleGroups.addBubble(bubble, message, false); // ! TEMP COMMENTED
this.bubbleGroups.changeBubbleMid(bubble, mid);
return;
}
@ -266,10 +270,18 @@ export default class ChatBubbles {
}
}
if(message.media?.document && !message.media.document.type) {
if(message.media?.document) {
const div = bubble.querySelector(`.document-container[data-mid="${tempId}"] .document`);
if(div) {
div.replaceWith(wrapDocument({message}));
const container = findUpClassName(div, 'document-container');
if(!tempMessage.media?.document?.thumbs?.length && message.media.document.thumbs?.length) {
div.replaceWith(wrapDocument({message}));
}
if(container) {
container.dataset.mid = '' + mid;
}
}
}
@ -434,6 +446,74 @@ export default class ChatBubbles {
// attachClickEvent(this.bubblesContainer, this.onBubblesClick, {listenerSetter: this.listenerSetter});
this.listenerSetter.add(this.bubblesContainer)('click', this.onBubblesClick/* , {capture: true, passive: false} */);
if(IS_TOUCH_SUPPORTED) {
const className = 'is-gesturing-reply';
const MAX = 64;
const replyAfter = MAX * .75;
let shouldReply = false;
let target: HTMLElement;
let icon: HTMLElement;
handleHorizontalSwipe({
element: this.bubblesContainer,
verifyTouchTarget: (e) => {
if(this.chat.selection.isSelecting || !this.appMessagesManager.canSendToPeer(this.peerId, this.chat.threadId)) {
return false;
}
// cancelEvent(e);
target = findUpClassName(e.target, 'bubble');
if(target) {
SetTransition(target, className, true, 250);
void target.offsetLeft; // reflow
if(!icon) {
icon = document.createElement('span');
icon.classList.add('tgico-reply_filled', 'bubble-gesture-reply-icon');
} else {
icon.classList.remove('is-visible');
icon.style.opacity = '';
}
target/* .querySelector('.bubble-content') */.append(icon);
}
return !!target;
},
onSwipe: (xDiff, yDiff) => {
shouldReply = xDiff >= replyAfter;
if(shouldReply && !icon.classList.contains('is-visible')) {
icon.classList.add('is-visible');
}
icon.style.opacity = '' + Math.min(1, xDiff / replyAfter);
const x = -Math.max(0, Math.min(MAX, xDiff));
target.style.transform = `translateX(${x}px)`;
cancelContextMenuOpening();
},
onReset: () => {
const _target = target;
SetTransition(_target, className, false, 250, () => {
if(icon.parentElement === _target) {
icon.classList.remove('is-visible');
icon.remove();
}
});
fastRaf(() => {
_target.style.transform = ``;
if(shouldReply) {
const {mid} = _target.dataset;
this.chat.input.initMessageReply(+mid);
shouldReply = false;
}
});
},
listenerOptions: {capture: true}
});
}
if(DEBUG) {
this.listenerSetter.add(this.bubblesContainer)('dblclick', (e) => {
const bubble = findUpClassName(e.target, 'grouped-item') || findUpClassName(e.target, 'bubble');
@ -540,7 +620,7 @@ export default class ChatBubbles {
});
this.listenerSetter.add(rootScope)('chat_update', (chatId) => {
if(this.peerId === -chatId) {
if(this.peerId === chatId.toPeerId(true)) {
const hadRights = this.chatInner.classList.contains('has-rights');
const hasRights = this.appMessagesManager.canSendToPeer(this.peerId, this.chat.threadId);
@ -551,7 +631,7 @@ export default class ChatBubbles {
}
});
this.listenerSetter.add(rootScope)('settings_updated', (e: BroadcastEvents['settings_updated']) => {
this.listenerSetter.add(rootScope)('settings_updated', (e) => {
if(e.key === 'settings.emoji.big') {
const isScrolledDown = this.scrollable.isScrolledDown;
if(!isScrolledDown) {
@ -872,7 +952,7 @@ export default class ChatBubbles {
const contactDiv: HTMLElement = findUpClassName(target, 'contact');
if(contactDiv) {
this.chat.appImManager.setInnerPeer(+contactDiv.dataset.peerId);
this.chat.appImManager.setInnerPeer(contactDiv.dataset.peerId.toPeerId());
return;
}
@ -890,7 +970,7 @@ export default class ChatBubbles {
const replies = message.replies;
if(replies) {
this.appMessagesManager.getDiscussionMessage(this.peerId, message.mid).then(message => {
this.chat.appImManager.setInnerPeer(-replies.channel_id, undefined, 'discussion', (message as MyMessage).mid);
this.chat.appImManager.setInnerPeer(replies.channel_id.toPeerId(true), undefined, 'discussion', (message as MyMessage).mid);
});
}
}
@ -901,21 +981,19 @@ export default class ChatBubbles {
const nameDiv = findUpClassName(target, 'peer-title') || findUpClassName(target, 'name') || findUpTag(target, 'AVATAR-ELEMENT');
if(nameDiv) {
target = nameDiv || target;
const peerId = +(target.dataset.peerId || target.getAttribute('peer'));
const peerId = (target.dataset.peerId || target.getAttribute('peer'));
const savedFrom = target.dataset.savedFrom;
if(nameDiv.classList.contains('is-via')) {
const message = '@' + this.appUsersManager.getUser(peerId).username + ' ';
this.appDraftsManager.setDraft(this.peerId, this.chat.threadId, message);
cancelEvent(e);
} else if(savedFrom) {
const splitted = savedFrom.split('_');
const peerId = +splitted[0];
const msgId = +splitted[1];
const [peerId, mid] = savedFrom.split('_');
this.chat.appImManager.setInnerPeer(peerId, msgId);
this.chat.appImManager.setInnerPeer(peerId.toPeerId(), +mid);
} else {
if(peerId) {
this.chat.appImManager.setInnerPeer(peerId);
this.chat.appImManager.setInnerPeer(peerId.toPeerId());
} else {
toast(I18n.format('HidAccount', true));
}
@ -967,7 +1045,7 @@ export default class ChatBubbles {
return media._ === 'photo' || ['video', 'gif'].includes(media.type);
};
const targets: {element: HTMLElement, mid: number, peerId: number}[] = [];
const targets: {element: HTMLElement, mid: number, peerId: PeerId}[] = [];
const ids = Object.keys(this.bubbles).map(k => +k).filter(id => {
//if(!this.scrollable.visibleElements.find(e => e.element === this.bubbles[id])) return false;
@ -1051,11 +1129,9 @@ export default class ChatBubbles {
if(['DIV', 'SPAN'].indexOf(target.tagName) !== -1/* || target.tagName === 'A' */) {
if(target.classList.contains('goto-original')) {
const savedFrom = bubble.dataset.savedFrom;
const splitted = savedFrom.split('_');
const peerId = +splitted[0];
const msgId = +splitted[1];
const [peerId, mid] = savedFrom.split('_');
////this.log('savedFrom', peerId, msgID);
this.chat.appImManager.setInnerPeer(peerId, msgId);
this.chat.appImManager.setInnerPeer(peerId.toPeerId(), +mid);
return;
} else if(target.classList.contains('forward')) {
const mid = +bubble.dataset.mid;
@ -1681,13 +1757,13 @@ export default class ChatBubbles {
////console.timeEnd('appImManager cleanup');
}
public setPeer(peerId: number, lastMsgId?: number): {cached?: boolean, promise: Chat['setPeerPromise']} {
public setPeer(peerId: PeerId, lastMsgId?: number): {cached?: boolean, promise: Chat['setPeerPromise']} {
//console.time('appImManager setPeer');
//console.time('appImManager setPeer pre promise');
////console.time('appImManager: pre render start');
if(!peerId) {
this.cleanup(true);
this.peerId = 0;
this.peerId = peerId;
return null;
}
@ -2186,6 +2262,10 @@ export default class ChatBubbles {
bubble.className = classNames.join(' ');
contentWrapper = bubble.lastElementChild as HTMLElement;
if(!contentWrapper.classList.contains('bubble-content-wrapper')) {
contentWrapper = bubble.querySelector('.bubble-content-wrapper');
}
bubbleContainer = contentWrapper.firstElementChild as HTMLDivElement;
bubbleContainer.innerHTML = '';
bubbleContainer.style.cssText = '';
@ -2242,9 +2322,9 @@ export default class ChatBubbles {
s.classList.add('service-msg');
if(action) {
if(action._ === 'messageActionChannelMigrateFrom') {
s.append(i18n('ChatMigration.From', [new PeerTitle({peerId: -action.chat_id}).element]));
s.append(i18n('ChatMigration.From', [new PeerTitle({peerId: action.chat_id.toPeerId(true)}).element]));
} else if(action._ === 'messageActionChatMigrateTo') {
s.append(i18n('ChatMigration.To', [new PeerTitle({peerId: -action.channel_id}).element]));
s.append(i18n('ChatMigration.To', [new PeerTitle({peerId: action.channel_id.toPeerId(true)}).element]));
} else {
s.append(this.appMessagesManager.wrapMessageActionTextNew(message));
}
@ -2376,28 +2456,19 @@ export default class ChatBubbles {
case 'keyboardButtonSwitchInline': {
buttonEl = document.createElement('button');
buttonEl.classList.add('is-switch-inline'/* , 'tgico' */);
const i = document.createElement('i');
i.classList.add('forward-icon');
i.innerHTML = `<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" preserveAspectRatio="xMidYMid meet" viewBox="0 0 24 24">
<defs>
<path d="M13.55 3.24L13.64 3.25L13.73 3.27L13.81 3.29L13.9 3.32L13.98 3.35L14.06 3.39L14.14 3.43L14.22 3.48L14.29 3.53L14.36 3.59L14.43 3.64L22.23 10.85L22.36 10.99L22.48 11.15L22.57 11.31L22.64 11.48L22.69 11.66L22.72 11.85L22.73 12.04L22.71 12.22L22.67 12.41L22.61 12.59L22.53 12.76L22.42 12.93L22.29 13.09L22.23 13.15L14.43 20.36L14.28 20.48L14.12 20.58L13.95 20.66L13.77 20.72L13.58 20.76L13.4 20.77L13.22 20.76L13.03 20.73L12.85 20.68L12.68 20.61L12.52 20.52L12.36 20.4L12.22 20.27L12.16 20.2L12.1 20.13L12.05 20.05L12.01 19.98L11.96 19.9L11.93 19.82L11.89 19.73L11.87 19.65L11.84 19.56L11.83 19.47L11.81 19.39L11.81 19.3L11.8 19.2L11.8 16.42L11 16.49L10.23 16.58L9.51 16.71L8.82 16.88L8.18 17.09L7.57 17.33L7.01 17.6L6.48 17.91L5.99 18.26L5.55 18.64L5.14 19.05L4.77 19.51L4.43 19.99L4.29 20.23L4.21 20.35L4.11 20.47L4 20.57L3.88 20.65L3.75 20.72L3.62 20.78L3.48 20.82L3.33 20.84L3.19 20.84L3.04 20.83L2.9 20.79L2.75 20.74L2.62 20.68L2.53 20.62L2.45 20.56L2.38 20.5L2.31 20.43L2.25 20.36L2.2 20.28L2.15 20.19L2.11 20.11L2.07 20.02L2.04 19.92L2.02 19.83L2.01 19.73L2 19.63L2.04 17.99L2.19 16.46L2.46 15.05L2.85 13.75L3.35 12.58L3.97 11.53L4.7 10.6L5.55 9.8L6.51 9.12L7.59 8.56L8.77 8.13L10.07 7.83L11.48 7.65L11.8 7.63L11.8 4.8L11.91 4.56L12.02 4.35L12.14 4.16L12.25 3.98L12.37 3.82L12.48 3.68L12.61 3.56L12.73 3.46L12.85 3.38L12.98 3.31L13.11 3.27L13.24 3.24L13.37 3.23L13.46 3.23L13.55 3.24Z" id="b13RmHDQtl"></path>
</defs>
<use xlink:href="#b13RmHDQtl" opacity="1" fill="#fff" fill-opacity="1"></use>
</svg>`;
buttonEl.append(i);
buttonEl.classList.add('is-switch-inline', 'tgico');
attachClickEvent(buttonEl, (e) => {
cancelEvent(e);
const botId = message.viaBotId || message.fromId;
let promise: Promise<number>;
let promise: Promise<PeerId>;
if(button.pFlags.same_peer) promise = Promise.resolve(this.peerId);
else promise = this.appInlineBotsManager.checkSwitchReturn(botId).then(peerId => {
if(peerId) {
return peerId;
}
return new Promise<number>((resolve, reject) => {
return new Promise<PeerId>((resolve, reject) => {
new PopupForward({
[this.peerId]: []
}, (peerId) => {
@ -2883,8 +2954,8 @@ export default class ChatBubbles {
let savedFrom = '';
// const needName = ((peerId < 0 && (peerId !== message.fromId || our)) && message.fromId !== rootScope.myId) || message.viaBotId;
const needName = (message.fromId !== rootScope.myId && peerId < 0 && !this.appPeersManager.isBroadcast(peerId)) || message.viaBotId;
// const needName = ((peerId.isAnyChat() && (peerId !== message.fromId || our)) && message.fromId !== rootScope.myId) || message.viaBotId;
const needName = (message.fromId !== rootScope.myId && this.appPeersManager.isAnyChat(peerId) && !this.appPeersManager.isBroadcast(peerId)) || message.viaBotId;
if(needName || message.fwd_from || message.reply_to_mid) { // chat
let title: HTMLElement | DocumentFragment;
@ -3498,12 +3569,12 @@ export default class ChatBubbles {
elements.push(b, '\n\n');
} else if(this.appPeersManager.isAnyGroup(this.peerId) && this.appPeersManager.getPeer(this.peerId).pFlags.creator) {
this.renderEmptyPlaceholder('group', bubble, message, elements);
} else if(rootScope.myId === this.peerId) {
this.renderEmptyPlaceholder('saved', bubble, message, elements);
} else if(this.peerId > 0 && !isBot && this.appMessagesManager.canSendToPeer(this.peerId) && this.chat.type === 'chat') {
this.renderEmptyPlaceholder('greeting', bubble, message, elements);
} else if(this.chat.type === 'scheduled') {
this.renderEmptyPlaceholder('noScheduledMessages', bubble, message, elements);
} else if(rootScope.myId === this.peerId) {
this.renderEmptyPlaceholder('saved', bubble, message, elements);
} else if(this.appPeersManager.isUser(this.peerId) && !isBot && this.appMessagesManager.canSendToPeer(this.peerId) && this.chat.type === 'chat') {
this.renderEmptyPlaceholder('greeting', bubble, message, elements);
} else {
this.renderEmptyPlaceholder('noMessages', bubble, message, elements);
}
@ -3537,7 +3608,7 @@ export default class ChatBubbles {
const message: Omit<Message.message | Message.messageService, 'message'> & {message?: string} = {
_: service ? 'messageService' : 'message',
date: 0,
id: -(this.peerId + offset),
id: -(+this.peerId + offset),
peer_id: this.appPeersManager.getOutputPeer(this.peerId),
pFlags: {}
};
@ -3566,11 +3637,11 @@ export default class ChatBubbles {
return;
} */
if(side === 'top' && value && this.appUsersManager.isBot(this.peerId)) {
if(side === 'top' && value && this.appPeersManager.isBot(this.peerId)) {
this.log('inject bot description');
const middleware = this.getMiddleware();
return this.appProfileManager.getProfile(this.peerId).then(userFull => {
return this.appProfileManager.getProfile(this.peerId.toUserId()).then(userFull => {
if(!middleware()) {
return;
}

View File

@ -33,7 +33,7 @@ import ChatContextMenu from "./contextMenu";
import ChatInput from "./input";
import ChatSelection from "./selection";
import ChatTopbar from "./topbar";
import { REPLIES_PEER_ID } from "../../lib/mtproto/mtproto_config";
import { NULL_PEER_ID, REPLIES_PEER_ID } from "../../lib/mtproto/mtproto_config";
import SetTransition from "../singleTransition";
import { fastRaf } from "../../helpers/schedulers";
import AppPrivateSearchTab from "../sidebarRight/tabs/search";
@ -57,7 +57,7 @@ export default class Chat extends EventListenerBase<{
public wasAlreadyUsed = false;
// public initPeerId = 0;
public peerId = 0;
public peerId: PeerId;
public threadId: number;
public setPeerPromise: Promise<void>;
public peerChanged: boolean;
@ -170,7 +170,7 @@ export default class Chat extends EventListenerBase<{
}
}
public init(/* peerId: number */) {
public init(/* peerId: PeerId */) {
// this.initPeerId = peerId;
this.topbar = new ChatTopbar(this, appSidebarRight, this.appMessagesManager, this.appPeersManager, this.appChatsManager, this.appNotificationsManager, this.appProfileManager, this.appUsersManager);
@ -217,7 +217,7 @@ export default class Chat extends EventListenerBase<{
this.bubbles.listenerSetter.add(rootScope)('dialog_drop', (e) => {
if(e.peerId === this.peerId) {
this.appImManager.setPeer(0);
this.appImManager.setPeer(NULL_PEER_ID);
}
});
}
@ -249,7 +249,7 @@ export default class Chat extends EventListenerBase<{
this.selection.cleanup();
}
public setPeer(peerId: number, lastMsgId?: number) {
public setPeer(peerId: PeerId, lastMsgId?: number) {
if(!peerId) {
this.inited = false;
} else if(!this.inited) {
@ -324,15 +324,21 @@ export default class Chat extends EventListenerBase<{
}
public setAutoDownloadMedia() {
const peerId = this.peerId;
if(!peerId) {
return;
}
let type: keyof State['settings']['autoDownload'];
if(this.peerId < 0) {
if(this.appPeersManager.isBroadcast(this.peerId)) {
if(!peerId.isUser()) {
if(peerId.isBroadcast()) {
type = 'channels';
} else {
type = 'groups';
}
} else {
if(this.appUsersManager.isContact(this.peerId)) {
if(peerId.isContact()) {
type = 'contacts';
} else {
type = 'private';

View File

@ -31,7 +31,7 @@ export default class CommandsHelper extends AutocompletePeerHelper {
);
}
public checkQuery(query: string, peerId: number) {
public checkQuery(query: string, peerId: PeerId) {
if(!this.appUsersManager.isBot(peerId)) {
return false;
}
@ -47,12 +47,12 @@ export default class CommandsHelper extends AutocompletePeerHelper {
ignoreCase: true
});
const commands: Map<string, {peerId: number, name: string, description: string}> = new Map();
const commands: Map<string, {peerId: PeerId, name: string, description: string}> = new Map();
botInfos.forEach(botInfo => {
botInfo.commands.forEach(botCommand => {
const c = '/' + botCommand.command;
commands.set(botCommand.command, {
peerId: botInfo.user_id,
peerId: botInfo.user_id.toPeerId(false),
name: c,
description: botCommand.description
});

View File

@ -6,7 +6,7 @@
import type { AppMessagesManager } from "../../lib/appManagers/appMessagesManager";
import type { AppPeersManager } from "../../lib/appManagers/appPeersManager";
import type { AppPollsManager, Poll } from "../../lib/appManagers/appPollsManager";
import type { AppPollsManager } from "../../lib/appManagers/appPollsManager";
import type { AppDocsManager, MyDocument } from "../../lib/appManagers/appDocsManager";
import type { AppMessagesIdsManager } from "../../lib/appManagers/appMessagesIdsManager";
import type Chat from "./chat";
@ -24,7 +24,7 @@ import findUpClassName from "../../helpers/dom/findUpClassName";
import { cancelEvent } from "../../helpers/dom/cancelEvent";
import { attachClickEvent, simulateClickEvent } from "../../helpers/dom/clickEvent";
import isSelectionEmpty from "../../helpers/dom/isSelectionEmpty";
import { Message } from "../../layer";
import { Message, Poll } from "../../layer";
import PopupReportMessages from "../popups/reportMessages";
export default class ChatContextMenu {
@ -38,7 +38,7 @@ export default class ChatContextMenu {
private isTextSelected: boolean;
private isAnchorTarget: boolean;
private isUsernameTarget: boolean;
private peerId: number;
private peerId: PeerId;
private mid: number;
private message: any;
@ -130,7 +130,8 @@ export default class ChatContextMenu {
//appImManager.log('contextmenu', e, bubble, side);
positionMenu((e as TouchEvent).touches ? (e as TouchEvent).touches[0] : e as MouseEvent, this.element, side);
openBtnMenu(this.element, () => {
this.peerId = this.mid = 0;
this.mid = 0;
this.peerId = undefined;
this.target = null;
});
};
@ -417,7 +418,7 @@ export default class ChatContextMenu {
if(threadMessage) url += '?comment=' + msgId;
key = 'LinkCopied';
} else {
url += 'c/' + Math.abs(this.peerId) + '/' + msgId;
url += 'c/' + this.peerId.toChatId() + '/' + msgId;
if(threadMessage) url += '?thread=' + this.appMessagesIdsManager.getServerMessageId(threadMessage.mid);
key = 'LinkCopiedPrivateInfo';
}

View File

@ -35,7 +35,7 @@ export default class InlineHelper extends AutocompleteHelper {
private gifsMasonry: GifsMasonry;
private superStickerRenderer: SuperStickerRenderer;
private onChangeScreen: () => void;
public checkQuery: (peerId: number, username: string, query: string) => ReturnType<InlineHelper['_checkQuery']>;
public checkQuery: (peerId: PeerId, username: string, query: string) => ReturnType<InlineHelper['_checkQuery']>;
constructor(appendTo: HTMLElement,
controller: AutocompleteHelperController,
@ -50,7 +50,7 @@ export default class InlineHelper extends AutocompleteHelper {
const {peerId, botId, queryId} = this.list.dataset;
return this.chat.input.getReadyToSend(() => {
const queryAndResultIds = this.appInlineBotsManager.generateQId(queryId, (target as HTMLElement).dataset.resultId);
this.appInlineBotsManager.sendInlineResult(+peerId, +botId, queryAndResultIds, {
this.appInlineBotsManager.sendInlineResult(peerId.toPeerId(), botId, queryAndResultIds, {
clearDraft: true,
scheduleDate: this.chat.input.scheduleDate,
silent: this.chat.input.sendSilent,
@ -80,7 +80,7 @@ export default class InlineHelper extends AutocompleteHelper {
});
}
public _checkQuery = async(peerId: number, username: string, query: string) => {
public _checkQuery = async(peerId: PeerId, username: string, query: string) => {
const middleware = this.controller.getMiddleware();
const peer = await this.appUsersManager.resolveUsername(username);
@ -105,7 +105,7 @@ export default class InlineHelper extends AutocompleteHelper {
const list = this.list.cloneNode() as HTMLElement;
list.dataset.peerId = '' + peerId;
list.dataset.botId = '' + peer.id;
list.dataset.queryId = botResults.query_id;
list.dataset.queryId = '' + botResults.query_id;
const gifsMasonry = new GifsMasonry(null, ANIMATION_GROUP, this.scrollable, false);

View File

@ -81,6 +81,8 @@ import { copy } from '../../helpers/object';
import PopupPeer from '../popups/peer';
import MEDIA_MIME_TYPES_SUPPORTED from '../../environment/mediaMimeTypesSupport';
import appMediaPlaybackController from '../appMediaPlaybackController';
import { NULL_PEER_ID } from '../../lib/mtproto/mtproto_config';
import replaceContent from '../../helpers/dom/replaceContent';
const RECORD_MIN_TIME = 500;
const POSTING_MEDIA_NOT_ALLOWED = 'Posting media content isn\'t allowed in this group.';
@ -110,20 +112,22 @@ export default class ChatInput {
private replyKeyboard: ReplyKeyboard;
private attachMenu: HTMLButtonElement;
private attachMenuButtons: (ButtonMenuItemOptions & {verify: (peerId: number, threadId: number) => boolean})[];
private attachMenuButtons: (ButtonMenuItemOptions & {verify: (peerId: PeerId, threadId: number) => boolean})[];
private sendMenu: SendMenu;
private replyElements: {
container: HTMLElement,
cancelBtn: HTMLButtonElement
cancelBtn: HTMLButtonElement,
iconBtn: HTMLButtonElement
} = {} as any;
private getWebPagePromise: Promise<void>;
private willSendWebPage: WebPage = null;
private forwarding: {[fromPeerId: number]: number[]};
private forwarding: {[frompeerId: PeerId]: number[]};
public replyToMsgId: number;
public editMsgId: number;
public editMessage: Message.message;
private noWebPage: true;
public scheduleDate: number;
public sendSilent: true;
@ -301,9 +305,10 @@ export default class ChatInput {
this.replyElements.container = document.createElement('div');
this.replyElements.container.classList.add('reply-wrapper');
this.replyElements.cancelBtn = ButtonIcon('close reply-cancel');
this.replyElements.iconBtn = ButtonIcon('');
this.replyElements.cancelBtn = ButtonIcon('close reply-cancel', {noRipple: true});
this.replyElements.container.append(this.replyElements.cancelBtn);
this.replyElements.container.append(this.replyElements.iconBtn, this.replyElements.cancelBtn);
this.newMessageWrapper = document.createElement('div');
this.newMessageWrapper.classList.add('new-message-wrapper');
@ -392,7 +397,7 @@ export default class ChatInput {
onClick: () => {
new PopupCreatePoll(this.chat).show();
},
verify: (peerId, threadId) => peerId < 0 && this.appMessagesManager.canSendToPeer(peerId, threadId, 'send_polls')
verify: (peerId, threadId) => peerId.isAnyChat() && this.appMessagesManager.canSendToPeer(peerId, threadId, 'send_polls')
}];
this.attachMenu = ButtonMenuToggle({noRipple: true, listenerSetter: this.listenerSetter}, 'top-left', this.attachMenuButtons);
@ -649,7 +654,7 @@ export default class ChatInput {
const peerId = this.chat.peerId;
new PopupPinMessage(peerId, 0, true, () => {
this.chat.appImManager.setPeer(0); // * close tab
this.chat.appImManager.setPeer(NULL_PEER_ID); // * close tab
// ! костыль, это скроет закреплённые сообщения сразу, вместо того, чтобы ждать пока анимация перехода закончится
const originalChat = this.chat.appImManager.chat;
@ -690,7 +695,7 @@ export default class ChatInput {
}
public scheduleSending = (callback: () => void = this.sendMessage.bind(this, true), initDate = new Date()) => {
const canSendWhenOnline = this.chat.peerId > 0 && this.appUsersManager.isUserOnlineVisible(this.chat.peerId);
const canSendWhenOnline = rootScope.myId !== this.chat.peerId && this.chat.peerId.isUser() && this.appUsersManager.isUserOnlineVisible(this.chat.peerId);
new PopupSchedule(initDate, (timestamp) => {
const minTimestamp = (Date.now() / 1000 | 0) + 10;
@ -777,7 +782,10 @@ export default class ChatInput {
if(this.messageInputField.value === draft.rMessage && this.replyToMsgId === draft.reply_to_msg_id) return false;
this.clearHelper();
if(fromUpdate) {
this.clearHelper();
}
this.noWebPage = draft.pFlags.no_webpage;
if(draft.reply_to_msg_id) {
this.initMessageReply(draft.reply_to_msg_id);
@ -1195,7 +1203,7 @@ export default class ChatInput {
this.undoHistory.length = 0;
}
const urlEntities: Array<MessageEntity.messageEntityUrl | MessageEntity.messageEntityTextUrl> = entities.filter(e => e._ === 'messageEntityUrl' || e._ === 'messageEntityTextUrl') as any;
const urlEntities: Array<MessageEntity.messageEntityUrl | MessageEntity.messageEntityTextUrl> = !this.editMessage?.media && entities.filter(e => e._ === 'messageEntityUrl' || e._ === 'messageEntityTextUrl') as any;
if(urlEntities.length) {
for(const entity of urlEntities) {
let url: string;
@ -1376,7 +1384,7 @@ export default class ChatInput {
this.stickersHelper.checkEmoticon(value);
} else if(firstChar === '@') { // mentions
const topMsgId = this.chat.threadId ? this.appMessagesIdsManager.getServerMessageId(this.chat.threadId) : undefined;
if(this.mentionsHelper.checkQuery(query, this.chat.peerId > 0 ? 0 : this.chat.peerId, topMsgId)) {
if(this.mentionsHelper.checkQuery(query, this.chat.peerId.isUser() ? NULL_PEER_ID : this.chat.peerId, topMsgId)) {
foundHelper = this.mentionsHelper;
}
} else if(!matches[1] && firstChar === '/') { // commands
@ -1456,7 +1464,7 @@ export default class ChatInput {
this.sendMessage();
}
} else {
if(this.chat.peerId < 0 && !this.appMessagesManager.canSendToPeer(this.chat.peerId, this.chat.threadId, 'send_media')) {
if(this.chat.peerId.isAnyChat() && !this.appMessagesManager.canSendToPeer(this.chat.peerId, this.chat.threadId, 'send_media')) {
toast(POSTING_MEDIA_NOT_ALLOWED);
return;
}
@ -1569,7 +1577,7 @@ export default class ChatInput {
}
};
private onHelperCancel = (e?: Event) => {
private onHelperCancel = (e?: Event, force?: boolean) => {
if(e) {
cancelEvent(e);
}
@ -1593,6 +1601,24 @@ export default class ChatInput {
if(needReturn) return;
}
if(this.helperType === 'edit' && !force) {
const message = this.editMessage
const value = RichTextProcessor.parseMarkdown(this.messageInputField.value, []);
if(message.message !== value) {
new PopupPeer('discard-editing', {
buttons: [{
langKey: 'Alert.Confirm.Discard',
callback: () => {
this.onHelperCancel(undefined, true);
}
}],
descriptionLangKey: 'Chat.Edit.Cancel.Text'
}).show();
return;
}
}
this.clearHelper();
this.updateSendBtn();
};
@ -1726,7 +1752,7 @@ export default class ChatInput {
//return;
if(this.editMsgId) {
const message = this.chat.getMessage(this.editMsgId);
const message = this.editMessage;
if(!!value.trim() || message.media) {
this.appMessagesManager.editMessage(message, value, {
entities,
@ -1758,7 +1784,7 @@ export default class ChatInput {
const scheduleDate = this.scheduleDate;
setTimeout(() => {
for(const fromPeerId in forwarding) {
this.appMessagesManager.forwardMessages(peerId, +fromPeerId, forwarding[fromPeerId], {
this.appMessagesManager.forwardMessages(peerId, fromPeerId.toPeerId(), forwarding[fromPeerId], {
silent,
scheduleDate: scheduleDate
});
@ -1773,7 +1799,7 @@ export default class ChatInput {
document = this.appDocsManager.getDoc(document);
const flag = document.type === 'sticker' ? 'send_stickers' : (document.type === 'gif' ? 'send_gifs' : 'send_media');
if(this.chat.peerId < 0 && !this.appMessagesManager.canSendToPeer(this.chat.peerId, this.chat.threadId, flag)) {
if(this.chat.peerId.isAnyChat() && !this.appMessagesManager.canSendToPeer(this.chat.peerId, this.chat.threadId, flag)) {
toast(POSTING_MEDIA_NOT_ALLOWED);
return false;
}
@ -1817,7 +1843,7 @@ export default class ChatInput {
} */
public initMessageEditing(mid: number) {
const message = this.chat.getMessage(mid);
const message: Message.message = this.chat.getMessage(mid);
let input = RichTextProcessor.wrapDraftText(message.message, {entities: message.totalEntities});
const f = () => {
@ -1825,26 +1851,27 @@ export default class ChatInput {
this.setTopInfo('edit', f, i18n('AccDescrEditing'), replyFragment, input, message);
this.editMsgId = mid;
this.editMessage = message;
input = undefined;
};
f();
}
public initMessagesForward(fromPeerIdsMids: {[fromPeerId: number]: number[]}) {
public initMessagesForward(fromPeerIdsMids: {[fromPeerId: PeerId]: number[]}) {
const f = () => {
//const peerTitles: string[]
const fromPeerIds = Object.keys(fromPeerIdsMids).map(str => +str);
const smth: Set<string | number> = new Set();
const fromPeerIds = Object.keys(fromPeerIdsMids).map(fromPeerId => fromPeerId.toPeerId());
const smth: Set<string> = new Set();
let length = 0;
fromPeerIds.forEach(fromPeerId => {
const mids = fromPeerIdsMids[fromPeerId];
mids.forEach(mid => {
const message = this.appMessagesManager.getMessageByPeer(fromPeerId, mid);
const message: Message.message = this.appMessagesManager.getMessageByPeer(fromPeerId, mid);
if(message.fwd_from?.from_name && !message.fromId && !message.fwdFromId) {
smth.add(message.fwd_from.from_name);
smth.add('N' + message.fwd_from.from_name);
} else {
smth.add(message.fromId);
smth.add('P' + message.fromId);
}
});
@ -1853,8 +1880,10 @@ export default class ChatInput {
const onlyFirstName = smth.size > 2;
const peerTitles = [...smth].map(smth => {
return typeof(smth) === 'number' ?
new PeerTitle({peerId: smth, dialog: false, onlyFirstName}).element :
const type = smth[0];
smth = smth.slice(1);
return type === 'P' ?
new PeerTitle({peerId: smth.toPeerId(), dialog: false, onlyFirstName}).element :
(onlyFirstName ? smth.split(' ')[0] : smth);
});
@ -1895,6 +1924,10 @@ export default class ChatInput {
}
public initMessageReply(mid: number) {
if(this.replyToMsgId === mid) {
return;
}
let message: Message = this.chat.getMessage(mid);
const f = () => {
let peerTitleEl: HTMLElement;
@ -1937,9 +1970,12 @@ export default class ChatInput {
this.willSendWebPage = null;
}
this.replyToMsgId = undefined;
this.forwarding = undefined;
this.editMsgId = undefined;
if(type !== 'reply') {
this.replyToMsgId = undefined;
this.forwarding = undefined;
}
this.editMsgId = this.editMessage = undefined;
this.helperType = this.helperFunc = undefined;
if(this.chat.container.classList.contains('is-helper-active')) {
@ -1961,12 +1997,18 @@ export default class ChatInput {
});
}
public setTopInfo(type: ChatInputHelperType,
public setTopInfo(
type: ChatInputHelperType,
callerFunc: () => void,
title: Parameters<typeof wrapReply>[0] = '',
subtitle: Parameters<typeof wrapReply>[1] = '',
input?: string,
message?: any) {
message?: any
) {
if(this.willSendWebPage && type === 'reply') {
return;
}
if(type !== 'webpage') {
this.clearHelper(type);
this.helperType = type;
@ -1978,6 +2020,7 @@ export default class ChatInput {
replyParent.lastElementChild.remove();
}
this.replyElements.iconBtn.replaceWith(this.replyElements.iconBtn = ButtonIcon((type === 'webpage' ? 'link' : type) + ' active reply-icon', {noRipple: true}));
replyParent.append(wrapReply(title, subtitle, message));
this.chat.container.classList.add('is-helper-active');

View File

@ -21,7 +21,7 @@ export default class MentionsHelper extends AutocompletePeerHelper {
controller,
'mentions-helper',
(target) => {
const user = appUsersManager.getUser(+(target as HTMLElement).dataset.peerId);
const user = appUsersManager.getUser((target as HTMLElement).dataset.peerId);
let str = '', entity: MessageEntity;
if(user.username) {
str = '@' + user.username;
@ -41,12 +41,12 @@ export default class MentionsHelper extends AutocompletePeerHelper {
);
}
public checkQuery(query: string, peerId: number, topMsgId: number) {
public checkQuery(query: string, peerId: PeerId, topMsgId: number) {
const trimmed = query.trim(); // check that there is no whitespace
if(query.length !== trimmed.length) return false;
const middleware = this.controller.getMiddleware();
this.appProfileManager.getMentions(peerId ? -peerId : 0, trimmed, topMsgId).then(peerIds => {
this.appProfileManager.getMentions(peerId && peerId.toChatId(), trimmed, topMsgId).then(peerIds => {
if(!middleware()) return;
const username = trimmed.slice(1).toLowerCase();

View File

@ -31,14 +31,7 @@ export namespace MessageRender {
if(!message.fwd_from?.saved_from_msg_id && chat.type !== 'pinned') {
const forward = document.createElement('div');
forward.classList.add('bubble-beside-button', 'forward');
forward.innerHTML = `
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" preserveAspectRatio="xMidYMid meet" viewBox="0 0 24 24">
<defs>
<path d="M13.55 3.24L13.64 3.25L13.73 3.27L13.81 3.29L13.9 3.32L13.98 3.35L14.06 3.39L14.14 3.43L14.22 3.48L14.29 3.53L14.36 3.59L14.43 3.64L22.23 10.85L22.36 10.99L22.48 11.15L22.57 11.31L22.64 11.48L22.69 11.66L22.72 11.85L22.73 12.04L22.71 12.22L22.67 12.41L22.61 12.59L22.53 12.76L22.42 12.93L22.29 13.09L22.23 13.15L14.43 20.36L14.28 20.48L14.12 20.58L13.95 20.66L13.77 20.72L13.58 20.76L13.4 20.77L13.22 20.76L13.03 20.73L12.85 20.68L12.68 20.61L12.52 20.52L12.36 20.4L12.22 20.27L12.16 20.2L12.1 20.13L12.05 20.05L12.01 19.98L11.96 19.9L11.93 19.82L11.89 19.73L11.87 19.65L11.84 19.56L11.83 19.47L11.81 19.39L11.81 19.3L11.8 19.2L11.8 16.42L11 16.49L10.23 16.58L9.51 16.71L8.82 16.88L8.18 17.09L7.57 17.33L7.01 17.6L6.48 17.91L5.99 18.26L5.55 18.64L5.14 19.05L4.77 19.51L4.43 19.99L4.29 20.23L4.21 20.35L4.11 20.47L4 20.57L3.88 20.65L3.75 20.72L3.62 20.78L3.48 20.82L3.33 20.84L3.19 20.84L3.04 20.83L2.9 20.79L2.75 20.74L2.62 20.68L2.53 20.62L2.45 20.56L2.38 20.5L2.31 20.43L2.25 20.36L2.2 20.28L2.15 20.19L2.11 20.11L2.07 20.02L2.04 19.92L2.02 19.83L2.01 19.73L2 19.63L2.04 17.99L2.19 16.46L2.46 15.05L2.85 13.75L3.35 12.58L3.97 11.53L4.7 10.6L5.55 9.8L6.51 9.12L7.59 8.56L8.77 8.13L10.07 7.83L11.48 7.65L11.8 7.63L11.8 4.8L11.91 4.56L12.02 4.35L12.14 4.16L12.25 3.98L12.37 3.82L12.48 3.68L12.61 3.56L12.73 3.46L12.85 3.38L12.98 3.31L13.11 3.27L13.24 3.24L13.37 3.23L13.46 3.23L13.55 3.24Z" id="b13RmHDQtl"></path>
</defs>
<use xlink:href="#b13RmHDQtl" opacity="1" fill="#fff" fill-opacity="1"></use>
</svg>`;
forward.classList.add('bubble-beside-button', 'forward', 'tgico-forward_filled');
bubbleContainer.append(forward);
bubble.classList.add('with-beside-button');
}

View File

@ -14,6 +14,7 @@ import { ripple } from "../ripple";
import AvatarElement from "../avatar";
import { i18n } from "../../lib/langPack";
import replaceContent from "../../helpers/dom/replaceContent";
import appChatsManager from "../../lib/appManagers/appChatsManager";
const TAG_NAME = 'replies-element';
@ -117,7 +118,7 @@ export default class RepliesElement extends HTMLElement {
}
if(replies) {
const historyStorage = appMessagesManager.getHistoryStorage(-replies.channel_id);
const historyStorage = appMessagesManager.getHistoryStorage(replies.channel_id.toPeerId(true));
let isUnread = false;
if(replies.replies) {
if(replies.read_max_id !== undefined && replies.max_id !== undefined) {

View File

@ -52,7 +52,7 @@ export function wrapReplyDivAndCaption(options: {
media = media.webpage;
}
if(media.photo || (media.document && ['video', 'sticker', 'gif', 'round', 'photo'].indexOf(media.document.type) !== -1)) {
if(media.photo || (media.document && media.document.thumbs?.length)/* ['video', 'sticker', 'gif', 'round', 'photo', 'audio'].indexOf(media.document.type) !== -1) */) {
middleware = appImManager.chat.bubbles.getMiddleware();
const lazyLoadQueue = appImManager.chat.bubbles.lazyLoadQueue;

View File

@ -24,7 +24,7 @@ export default class ReplyKeyboard extends DropdownHover {
private listenerSetter: ListenerSetter;
private appMessagesManager: AppMessagesManager;
private btnHover: HTMLElement;
private peerId: number;
private peerId: PeerId;
private touchListener: Listener;
private chatInput: ChatInput;
@ -139,7 +139,7 @@ export default class ReplyKeyboard extends DropdownHover {
return !hide;
}
public setPeer(peerId: number) {
public setPeer(peerId: PeerId) {
this.peerId = peerId;
this.checkAvailability();

View File

@ -140,7 +140,7 @@ export default class ChatSearch {
selectResult = (elem: HTMLElement) => {
if(this.setPeerPromise) return this.setPeerPromise;
const peerId = +elem.dataset.peerId;
const peerId = elem.dataset.peerId.toPeerId();
const lastMsgId = +elem.dataset.mid || undefined;
const index = whichChild(elem);

View File

@ -37,14 +37,14 @@ import { attachContextMenuListener } from "../misc";
import { attachClickEvent, AttachClickOptions } from "../../helpers/dom/clickEvent";
import findUpAsChild from "../../helpers/dom/findUpAsChild";
const accumulateMapSet = (map: Map<number, Set<number>>) => {
const accumulateMapSet = (map: Map<any, Set<number>>) => {
return [...map.values()].reduce((acc, v) => acc + v.size, 0);
};
//const MIN_CLICK_MOVE = 32; // minimum bubble height
class AppSelection {
public selectedMids: Map<number, Set<number>> = new Map();
public selectedMids: Map<PeerId, Set<number>> = new Map();
public isSelecting = false;
public selectedText: string;
@ -57,7 +57,7 @@ class AppSelection {
protected onToggleSelection: (forwards: boolean) => void;
protected onUpdateContainer: (cantForward: boolean, cantDelete: boolean, cantSend: boolean) => void;
protected onCancelSelection: () => void;
protected toggleByMid: (peerId: number, mid: number) => void;
protected toggleByMid: (peerId: PeerId, mid: number) => void;
protected toggleByElement: (bubble: HTMLElement) => void;
protected navigationType: NavigationItem['type'];
@ -157,7 +157,7 @@ class AppSelection {
return;
}
const seen: Map<number, Set<number>> = new Map();
const seen: AppSelection['selectedMids'] = new Map();
let selecting: boolean;
/* let good = false;
@ -175,7 +175,7 @@ class AppSelection {
const processElement = (element: HTMLElement, checkBetween = true) => {
const mid = +element.dataset.mid;
const peerId = +element.dataset.peerId;
const peerId = (element.dataset.peerId || '').toPeerId();
if(!mid || !peerId) return;
if(!isInDOM(firstTarget)) {
@ -280,7 +280,7 @@ class AppSelection {
}
protected isElementShouldBeSelected(element: HTMLElement) {
return this.isMidSelected(+element.dataset.peerId, +element.dataset.mid);
return this.isMidSelected(element.dataset.peerId.toPeerId(), +element.dataset.mid);
}
protected appendCheckbox(element: HTMLElement, checkboxField: CheckboxField) {
@ -435,7 +435,7 @@ class AppSelection {
SetTransition(element, 'is-selected', isSelected, 200);
}
public isMidSelected(peerId: number, mid: number) {
public isMidSelected(peerId: PeerId, mid: number) {
const set = this.selectedMids.get(peerId);
return set?.has(mid);
}
@ -444,7 +444,7 @@ class AppSelection {
return accumulateMapSet(this.selectedMids);
}
protected toggleMid(peerId: number, mid: number, unselect?: boolean) {
protected toggleMid(peerId: PeerId, mid: number, unselect?: boolean) {
let set = this.selectedMids.get(peerId);
if(unselect || (unselect === undefined && set?.has(mid))) {
if(set) {
@ -488,7 +488,7 @@ class AppSelection {
/**
* ! Call this method only to handle deleted messages
*/
public deleteSelectedMids(peerId: number, mids: number[]) {
public deleteSelectedMids(peerId: PeerId, mids: number[]) {
const set = this.selectedMids.get(peerId);
if(!set) {
return;
@ -556,7 +556,7 @@ export class SearchSelection extends AppSelection {
public toggleByElement = (element: HTMLElement) => {
const mid = +element.dataset.mid;
const peerId = +element.dataset.peerId;
const peerId = element.dataset.peerId.toPeerId();
if(!this.toggleMid(peerId, mid)) {
return;
@ -565,7 +565,7 @@ export class SearchSelection extends AppSelection {
this.updateElementSelection(element, this.isMidSelected(peerId, mid));
};
public toggleByMid = (peerId: number, mid: number) => {
public toggleByMid = (peerId: PeerId, mid: number) => {
const element = this.searchSuper.mediaTab.contentTab.querySelector(`.search-super-item[data-peer-id="${peerId}"][data-mid="${mid}"]`) as HTMLElement;
this.toggleByElement(element);
};
@ -620,7 +620,7 @@ export class SearchSelection extends AppSelection {
this.selectionForwardBtn = ButtonIcon(`forward ${BASE_CLASS}-forward`);
attachClickEvent(this.selectionForwardBtn, () => {
const obj: {[fromPeerId: number]: number[]} = {};
const obj: {[frompeerId: PeerId]: number[]} = {};
for(const [fromPeerId, mids] of this.selectedMids) {
obj[fromPeerId] = Array.from(mids);
}
@ -773,7 +773,7 @@ export default class ChatSelection extends AppSelection {
this.updateElementSelection(bubble, this.isMidSelected(this.bubbles.peerId, mid));
};
protected toggleByMid = (peerId: number, mid: number) => {
protected toggleByMid = (peerId: PeerId, mid: number) => {
const mounted = this.bubbles.getMountedBubble(mid);
if(mounted) {
this.toggleByElement(mounted.bubble);
@ -895,7 +895,7 @@ export default class ChatSelection extends AppSelection {
this.selectionForwardBtn = Button('btn-primary btn-transparent text-bold selection-container-forward', {icon: 'forward'});
this.selectionForwardBtn.append(i18n('Forward'));
attachClickEvent(this.selectionForwardBtn, () => {
const obj: {[fromPeerId: number]: number[]} = {};
const obj: {[frompeerId: PeerId]: number[]} = {};
for(const [fromPeerId, mids] of this.selectedMids) {
obj[fromPeerId] = Array.from(mids);
}

View File

@ -57,7 +57,7 @@ export default class SendMenu {
}, options.listenerSetter);
}
public setPeerId(peerId: number) {
public setPeerId(peerId: PeerId) {
this.type = peerId === rootScope.myId ? 'reminder' : 'schedule';
}
};

View File

@ -44,6 +44,7 @@ import generateVerifiedIcon from "../generateVerifiedIcon";
import { fastRaf } from "../../helpers/schedulers";
import AppEditContactTab from "../sidebarRight/tabs/editContact";
import appMediaPlaybackController from "../appMediaPlaybackController";
import { NULL_PEER_ID } from "../../lib/mtproto/mtproto_config";
export default class ChatTopbar {
public container: HTMLDivElement;
@ -63,8 +64,8 @@ export default class ChatTopbar {
public pinnedMessage: ChatPinnedMessage;
private setUtilsRAF: number;
public peerId: number;
private wasPeerId: number;
public peerId: PeerId;
private wasPeerId: PeerId;
private setPeerStatusInterval: number;
public listenerSetter: ListenerSetter;
@ -156,7 +157,8 @@ export default class ChatTopbar {
this.container.append(this.btnBack, this.chatInfo, this.chatUtils);
if(this.chatAudio) {
this.container.append(this.chatAudio.divAndCaption.container, this.chatUtils);
// this.container.append(this.chatAudio.divAndCaption.container, this.chatUtils);
this.container.append(this.chatAudio.divAndCaption.container);
}
// * construction end
@ -177,12 +179,12 @@ export default class ChatTopbar {
}
const mid = +container.dataset.mid;
const peerId = +container.dataset.peerId;
if(container.classList.contains('pinned-message')) {
//if(!this.pinnedMessage.locked) {
this.pinnedMessage.followPinnedMessage(mid);
//}
} else {
const peerId = container.dataset.peerId.toPeerId();
const searchContext = appMediaPlaybackController.getSearchContext();
this.chat.appImManager.setInnerPeer(
peerId,
@ -214,14 +216,14 @@ export default class ChatTopbar {
} else {
const isFirstChat = this.chat.appImManager.chats.indexOf(this.chat) === 0;
appNavigationController.back(isFirstChat ? 'im' : 'chat');
return;
/* return;
if(mediaSizes.activeScreen === ScreenSize.medium && !appNavigationController.findItemByType('chat')) {
this.chat.appImManager.setPeer(0);
blurActiveElement();
} else {
appNavigationController.back('chat');
}
} */
}
};
@ -259,14 +261,14 @@ export default class ChatTopbar {
icon: 'comments',
text: 'ViewDiscussion',
onClick: () => {
this.appProfileManager.getChannelFull(-this.peerId).then(channelFull => {
this.appProfileManager.getChannelFull(this.peerId.toChatId()).then(channelFull => {
if(channelFull.linked_chat_id) {
this.chat.appImManager.setInnerPeer(-channelFull.linked_chat_id);
this.chat.appImManager.setInnerPeer(channelFull.linked_chat_id.toPeerId(true));
}
});
},
verify: () => {
const chatFull = this.appProfileManager.chatsFull[-this.peerId];
const chatFull = this.appProfileManager.chatsFull[this.peerId.toChatId()];
return this.chat.type === 'chat' && this.appPeersManager.isBroadcast(this.peerId) && !!(chatFull as ChatFull.channelFull)?.linked_chat_id;
}
}, {
@ -310,7 +312,7 @@ export default class ChatTopbar {
this.appSidebarRight.toggleSidebar(true);
}
},
verify: () => this.peerId > 0 && !this.appUsersManager.isContact(this.peerId)
verify: () => this.appPeersManager.isContact(this.peerId)
}, {
icon: 'forward',
text: 'ShareContact',
@ -349,7 +351,7 @@ export default class ChatTopbar {
selfPresence: 'ChatYourSelf'
});
},
verify: () => rootScope.myId !== this.peerId && this.peerId > 0 && this.appUsersManager.isContact(this.peerId)
verify: () => rootScope.myId !== this.peerId && this.appPeersManager.isContact(this.peerId)
}, {
icon: 'lock',
text: 'BlockUser',
@ -373,8 +375,9 @@ export default class ChatTopbar {
}).show();
},
verify: () => {
const userFull = this.appProfileManager.usersFull[this.peerId];
return this.peerId > 0 && this.peerId !== rootScope.myId && userFull && !userFull.pFlags?.blocked;
const userId = this.peerId.toUserId();
const userFull = this.appProfileManager.usersFull[userId];
return this.appPeersManager.isUser(this.peerId) && this.peerId !== rootScope.myId && userFull && !userFull.pFlags?.blocked;
}
}, {
icon: 'lockoff',
@ -387,8 +390,8 @@ export default class ChatTopbar {
});
},
verify: () => {
const userFull = this.appProfileManager.usersFull[this.peerId];
return this.peerId > 0 && !!userFull?.pFlags?.blocked;
const userFull = this.appProfileManager.usersFull[this.peerId.toUserId()];
return this.appPeersManager.isUser(this.peerId) && !!userFull?.pFlags?.blocked;
}
}, {
icon: 'delete danger',
@ -440,7 +443,7 @@ export default class ChatTopbar {
const middleware = this.chat.bubbles.getMiddleware();
this.btnJoin.setAttribute('disabled', 'true');
const chatId = -this.peerId;
const chatId = this.peerId.toChatId();
let promise: Promise<any>;
if(this.appChatsManager.isChannel(chatId)) {
promise = this.appChatsManager.joinChannel(chatId);
@ -458,7 +461,7 @@ export default class ChatTopbar {
}, {listenerSetter: this.listenerSetter});
this.listenerSetter.add(rootScope)('chat_update', (chatId) => {
if(this.peerId === -chatId) {
if(this.peerId === chatId.toPeerId(true)) {
const chat = this.appChatsManager.getChat(chatId) as Channel/* | Chat */;
this.btnJoin.classList.toggle('hide', !(chat as Channel)?.pFlags?.left);
@ -554,7 +557,7 @@ export default class ChatTopbar {
delete this.pinnedMessage;
}
public setPeer(peerId: number) {
public setPeer(peerId: PeerId) {
this.wasPeerId = this.peerId;
this.peerId = peerId;
@ -570,12 +573,15 @@ export default class ChatTopbar {
}
const isBroadcast = this.appPeersManager.isBroadcast(peerId);
this.btnMute && this.btnMute.classList.toggle('hide', !isBroadcast);
if(this.btnJoin) {
replaceContent(this.btnJoin, i18n(this.appChatsManager.isChannel(-peerId) ? 'Chat.Subscribe' : 'ChannelJoin'));
this.btnJoin.classList.toggle('hide', !this.appChatsManager.getChat(-peerId)?.pFlags?.left);
if(this.appPeersManager.isAnyChat(peerId)) {
if(this.btnJoin) {
const chatId = peerId.toChatId();
replaceContent(this.btnJoin, i18n(this.appChatsManager.isChannel(chatId) ? 'Chat.Subscribe' : 'ChannelJoin'));
this.btnJoin.classList.toggle('hide', !this.appChatsManager.getChat(chatId)?.pFlags?.left);
}
}
this.setUtilsWidth();
const middleware = this.chat.bubbles.getMiddleware();
@ -626,7 +632,7 @@ export default class ChatTopbar {
// ! костыль х2, это нужно делать в другом месте
if(!count) {
this.chat.appImManager.setPeer(0); // * close tab
this.chat.appImManager.setPeer(NULL_PEER_ID); // * close tab
// ! костыль, это скроет закреплённые сообщения сразу, вместо того, чтобы ждать пока анимация перехода закончится
const originalChat = this.chat.appImManager.chat;

View File

@ -23,7 +23,7 @@ export default class DialogsContextMenu {
private element: HTMLElement;
private buttons: (ButtonMenuItemOptions & {verify: () => boolean})[];
private selectedId: number;
private selectedId: PeerId;
private filterId: number;
private dialog: Dialog;
@ -32,24 +32,18 @@ export default class DialogsContextMenu {
icon: 'unread',
text: 'MarkAsUnread',
onClick: this.onUnreadClick,
verify: () => {
const isUnread = !!(this.dialog.pFlags?.unread_mark || this.dialog.unread_count);
return !isUnread;
}
verify: () => !appMessagesManager.isDialogUnread(this.dialog)
}, {
icon: 'readchats',
text: 'MarkAsRead',
onClick: this.onUnreadClick,
verify: () => {
const isUnread = !!(this.dialog.pFlags?.unread_mark || this.dialog.unread_count);
return isUnread;
}
verify: () => appMessagesManager.isDialogUnread(this.dialog)
}, {
icon: 'pin',
text: 'ChatList.Context.Pin',
onClick: this.onPinClick,
verify: () => {
const isPinned = this.filterId > 1 ? appMessagesManager.filtersStorage.getFilter(this.filterId).pinned_peers.includes(this.dialog.peerId) : !!this.dialog.pFlags?.pinned;
const isPinned = this.filterId > 1 ? appMessagesManager.filtersStorage.getFilter(this.filterId).pinnedPeerIds.includes(this.dialog.peerId) : !!this.dialog.pFlags?.pinned;
return !isPinned;
}
}, {
@ -57,7 +51,7 @@ export default class DialogsContextMenu {
text: 'ChatList.Context.Unpin',
onClick: this.onPinClick,
verify: () => {
const isPinned = this.filterId > 1 ? appMessagesManager.filtersStorage.getFilter(this.filterId).pinned_peers.includes(this.dialog.peerId) : !!this.dialog.pFlags?.pinned;
const isPinned = this.filterId > 1 ? appMessagesManager.filtersStorage.getFilter(this.filterId).pinnedPeerIds.includes(this.dialog.peerId) : !!this.dialog.pFlags?.pinned;
return isPinned;
}
}, {
@ -174,7 +168,7 @@ export default class DialogsContextMenu {
this.filterId = appDialogsManager.filterId;
this.selectedId = +li.dataset.peerId;
this.selectedId = li.dataset.peerId.toPeerId();
this.dialog = appMessagesManager.getDialogOnly(this.selectedId);
this.buttons.forEach(button => {

View File

@ -22,13 +22,13 @@ export default class EditPeer {
private inputFields: InputField[];
private listenerSetter: ListenerSetter;
private peerId: number;
private peerId: PeerId;
private _disabled = false;
private avatarSize = 120;
constructor(options: {
peerId?: number,
peerId?: EditPeer['peerId'],
inputFields: EditPeer['inputFields'],
listenerSetter: ListenerSetter,
doNotEditAvatar?: boolean,

View File

@ -23,7 +23,7 @@ export default class GifsTab implements EmoticonsTab {
const masonry = new GifsMasonry(gifsContainer, EMOTICONSSTICKERGROUP, scroll);
const preloader = putPreloader(this.content, true);
apiManager.invokeApi('messages.getSavedGifs', {hash: 0}).then((res) => {
apiManager.invokeApi('messages.getSavedGifs', {hash: '0'}).then((res) => {
//console.log('getSavedGifs res:', res);
if(res._ === 'messages.savedGifs') {

View File

@ -198,8 +198,8 @@ export default class StickersTab implements EmoticonsTab {
async renderStickerSet(set: StickerSet.stickerSet, prepend = false) {
const categoryDiv = document.createElement('div');
categoryDiv.classList.add('sticker-category');
categoryDiv.dataset.id = set.id;
categoryDiv.dataset.access_hash = set.access_hash;
categoryDiv.dataset.id = '' + set.id;
categoryDiv.dataset.access_hash = '' + set.access_hash;
const button = document.createElement('button');
button.classList.add('btn-icon', 'menu-horizontal-div-item');

View File

@ -198,7 +198,7 @@ export default class GifsMasonry {
div.style.width = size.width + 'px';
div.style.opacity = '0';
//div.style.height = h + 'px';
div.dataset.docId = doc.id;
div.dataset.docId = '' + doc.id;
appendTo.append(div);

View File

@ -6,7 +6,7 @@
import { logger, LogTypes } from "../lib/logger";
import VisibilityIntersector, { OnVisibilityChange } from "./visibilityIntersector";
import { findAndSpliceAll } from "../helpers/array";
import { findAndSpliceAll, indexOfAndSplice } from "../helpers/array";
import throttle from "../helpers/schedulers/throttle";
type LazyLoadElementBase = {
@ -121,7 +121,7 @@ export class LazyLoadQueueBase {
let added = 0;
do {
if(item) {
this.queue.findAndSplice(i => i === item);
indexOfAndSplice(this.queue, item);
} else {
item = this.getItem();
}

View File

@ -52,7 +52,7 @@ export default class PeerProfile {
private setBioTimeout: number;
private setPeerStatusInterval: number;
private peerId = 0;
private peerId: PeerId;
private threadId: number;
constructor(public scrollable: Scrollable) {
@ -264,7 +264,7 @@ export default class PeerProfile {
}
//let membersLi = this.profileTabs.firstElementChild.children[0] as HTMLLIElement;
if(peerId > 0) {
if(peerId.isUser()) {
//membersLi.style.display = 'none';
let user = appUsersManager.getUser(peerId);
@ -304,7 +304,7 @@ export default class PeerProfile {
}
let promise: Promise<boolean>;
if(peerId > 0) {
if(peerId.isUser()) {
promise = appProfileManager.getProfile(peerId, override).then(userFull => {
if(this.peerId !== peerId || this.threadId !== threadId) {
//this.log.warn('peer changed');
@ -319,7 +319,7 @@ export default class PeerProfile {
return true;
});
} else {
promise = appProfileManager.getChatFull(-peerId, override).then((chatFull) => {
promise = appProfileManager.getChatFull(peerId.toChatId(), override).then((chatFull) => {
if(this.peerId !== peerId || this.threadId !== threadId) {
//this.log.warn('peer changed');
return false;
@ -342,8 +342,8 @@ export default class PeerProfile {
});
}
public setPeer(peerId: number, threadId = 0) {
if(this.peerId === peerId && this.threadId === peerId) return;
public setPeer(peerId: PeerId, threadId = 0) {
if(this.peerId === peerId && this.threadId === threadId) return;
if(this.init) {
this.init();

View File

@ -28,8 +28,8 @@ export default class PeerProfileAvatars {
public arrowPrevious: HTMLElement;
public arrowNext: HTMLElement;
private tabs: HTMLDivElement;
private listLoader: ListLoader<string | Message.messageService, string | Message.messageService>;
private peerId: number;
private listLoader: ListLoader<Photo.photo['id'] | Message.messageService, Photo.photo['id'] | Message.messageService>;
private peerId: PeerId;
constructor(public scrollable: Scrollable) {
this.container = document.createElement('div');
@ -48,14 +48,14 @@ export default class PeerProfileAvatars {
this.tabs.classList.add(PeerProfileAvatars.BASE_CLASS + '-tabs');
this.arrowPrevious = document.createElement('div');
this.arrowPrevious.classList.add(PeerProfileAvatars.BASE_CLASS + '-arrow');
this.arrowPrevious.classList.add(PeerProfileAvatars.BASE_CLASS + '-arrow', 'tgico-avatarprevious');
/* const previousIcon = document.createElement('i');
previousIcon.classList.add(PeerProfileAvatars.BASE_CLASS + '-arrow-icon', 'tgico-previous');
this.arrowBack.append(previousIcon); */
this.arrowNext = document.createElement('div');
this.arrowNext.classList.add(PeerProfileAvatars.BASE_CLASS + '-arrow', PeerProfileAvatars.BASE_CLASS + '-arrow-next');
this.arrowNext.classList.add(PeerProfileAvatars.BASE_CLASS + '-arrow', PeerProfileAvatars.BASE_CLASS + '-arrow-next', 'tgico-avatarnext');
/* const nextIcon = document.createElement('i');
nextIcon.classList.add(PeerProfileAvatars.BASE_CLASS + '-arrow-icon', 'tgico-next');
@ -101,7 +101,7 @@ export default class PeerProfileAvatars {
|| (clickX > (rect.width * SWITCH_ZONE) && clickX < (rect.width - rect.width * SWITCH_ZONE))) {
const peerId = this.peerId;
const targets: {element: HTMLElement, item: string | Message.messageService}[] = [];
const targets: {element: HTMLElement, item: Photo.photo['id'] | Message.messageService}[] = [];
this.listLoader.previous.concat(this.listLoader.current, this.listLoader.next).forEach((item, idx) => {
targets.push({
element: /* null */this.avatars.children[idx] as HTMLElement,
@ -199,7 +199,7 @@ export default class PeerProfileAvatars {
});
}
public setPeer(peerId: number) {
public setPeer(peerId: PeerId) {
this.peerId = peerId;
const photo = appPeersManager.getPeerPhoto(peerId);
@ -212,8 +212,8 @@ export default class PeerProfileAvatars {
loadMore: (anchor, older, loadCount) => {
if(!older) return Promise.resolve({count: undefined, items: []});
if(peerId > 0) {
const maxId: string = (anchor || listLoader.current) as any;
if(peerId.isUser()) {
const maxId: Photo.photo['id'] = (anchor || listLoader.current) as any;
return appPhotosManager.getUserPhotos(peerId, maxId, loadCount).then(value => {
return {
count: value.count,
@ -223,7 +223,7 @@ export default class PeerProfileAvatars {
} else {
const promises: [Promise<ChatFull>, ReturnType<AppMessagesManager['getSearch']>] = [] as any;
if(!listLoader.current) {
promises.push(appProfileManager.getChatFull(-peerId));
promises.push(appProfileManager.getChatFull(peerId.toChatId()));
}
promises.push(appMessagesManager.getSearch({
@ -295,13 +295,13 @@ export default class PeerProfileAvatars {
this.container.classList.toggle('is-single', this.tabs.childElementCount <= 1);
}
public processItem = (photoId: string | Message.messageService) => {
public processItem = (photoId: Photo.photo['id'] | Message.messageService) => {
const avatar = document.createElement('div');
avatar.classList.add(PeerProfileAvatars.BASE_CLASS + '-avatar');
let photo: Photo.photo;
if(photoId) {
photo = typeof(photoId) === 'string' ?
photo = typeof(photoId) !== 'object' ?
appPhotosManager.getPhoto(photoId) :
(photoId.action as MessageAction.messageActionChannelEditPhoto).photo as Photo.photo;
}

View File

@ -12,7 +12,7 @@ import replaceContent from "../helpers/dom/replaceContent";
import appUsersManager from "../lib/appManagers/appUsersManager";
export type PeerTitleOptions = {
peerId: number,
peerId: PeerId,
plainText?: boolean,
onlyFirstName?: boolean,
dialog?: boolean
@ -36,7 +36,7 @@ rootScope.addEventListener('peer_title_edit', (peerId) => {
export default class PeerTitle {
public element: HTMLElement;
public peerId: number;
public peerId: PeerId;
public plainText = false;
public onlyFirstName = false;
public dialog = false;
@ -61,7 +61,7 @@ export default class PeerTitle {
}
if(this.peerId !== rootScope.myId || !this.dialog) {
if(this.peerId > 0 && appUsersManager.getUser(this.peerId).pFlags.deleted) {
if(this.peerId.isUser() && appUsersManager.getUser(this.peerId).pFlags.deleted) {
replaceContent(this.element, i18n(this.onlyFirstName ? 'Deleted' : 'HiddenName'));
} else {
this.element.innerHTML = appPeersManager.getPeerTitle(this.peerId, this.plainText, this.onlyFirstName);

View File

@ -7,7 +7,7 @@
import mediaSizes from "../helpers/mediaSizes";
import { IS_TOUCH_SUPPORTED } from "../environment/touchSupport";
import appImManager from "../lib/appManagers/appImManager";
import appPollsManager, { Poll, PollResults } from "../lib/appManagers/appPollsManager";
import appPollsManager from "../lib/appManagers/appPollsManager";
import serverTimeManager from "../lib/mtproto/serverTimeManager";
import { RichTextProcessor } from "../lib/richtextprocessor";
import rootScope from "../lib/rootScope";
@ -22,6 +22,7 @@ import { cancelEvent } from "../helpers/dom/cancelEvent";
import { attachClickEvent, detachClickEvent } from "../helpers/dom/clickEvent";
import replaceContent from "../helpers/dom/replaceContent";
import windowSize from "../helpers/windowSize";
import { Poll, PollResults } from "../layer";
let lineTotalLength = 0;
const tailLength = 9;
@ -92,9 +93,7 @@ rootScope.on('poll_update', (e) => {
}
}); */
rootScope.addEventListener('poll_update', (e) => {
const {poll, results} = e as {poll: Poll, results: PollResults};
rootScope.addEventListener('poll_update', ({poll, results}) => {
const pollElements = Array.from(document.querySelectorAll(`poll-element[poll-id="${poll.id}"]`)) as PollElement[];
pollElements.forEach(pollElement => {
//console.log('poll_update', poll, results);

View File

@ -4,7 +4,6 @@
* https://github.com/morethanwords/tweb/blob/master/LICENSE
*/
import type { Poll } from "../../lib/appManagers/appPollsManager";
import type Chat from "../chat/chat";
import PopupElement from ".";
import CheckboxField from "../checkboxField";
@ -19,6 +18,7 @@ import getRichValue from "../../helpers/dom/getRichValue";
import isInputEmpty from "../../helpers/dom/isInputEmpty";
import whichChild from "../../helpers/dom/whichChild";
import { attachClickEvent } from "../../helpers/dom/clickEvent";
import { Poll } from "../../layer";
const MAX_LENGTH_QUESTION = 255;
const MAX_LENGTH_OPTION = 100;

View File

@ -13,7 +13,7 @@ import PopupPeer, { PopupPeerButtonCallbackCheckboxes, PopupPeerOptions } from "
export default class PopupDeleteDialog {
constructor(
peerId: number,
peerId: PeerId,
// actionType: 'leave' | 'delete',
peerType: PeerType = appPeersManager.getDialogType(peerId),
onSelect?: (promise: Promise<any>) => void
@ -26,7 +26,7 @@ export default class PopupDeleteDialog {
}; */
const callbackLeave = (checked: PopupPeerButtonCallbackCheckboxes, flush = checkboxes && !!checked.size) => {
let promise = appChatsManager.leave(-peerId);
let promise = appChatsManager.leave(peerId.toChatId());
if(flush) {
promise = promise.finally(() => {
@ -40,11 +40,11 @@ export default class PopupDeleteDialog {
const callbackDelete = (checked: PopupPeerButtonCallbackCheckboxes) => {
let promise: Promise<any>;
if(peerId > 0) {
if(peerId.isUser()) {
promise = appMessagesManager.flushHistory(peerId, false, checkboxes ? !!checked.size : undefined);
} else {
if(checked.size) {
promise = appChatsManager.delete(-peerId);
promise = appChatsManager.delete(peerId.toChatId());
} else {
return callbackLeave(checked);
}
@ -56,7 +56,7 @@ export default class PopupDeleteDialog {
let title: LangPackKey, description: LangPackKey, descriptionArgs: any[], buttons: PopupPeerOptions['buttons'], checkboxes: PopupPeerOptions['checkboxes'];
switch(peerType) {
case 'channel': {
if(/* actionType === 'delete' && */appChatsManager.hasRights(-peerId, 'delete_chat')) {
if(/* actionType === 'delete' && */appChatsManager.hasRights(peerId.toChatId(), 'delete_chat')) {
appChatsManager.deleteChannel
title = 'ChannelDeleteMenu';
description = 'AreYouSureDeleteAndExitChannel';
@ -130,7 +130,7 @@ export default class PopupDeleteDialog {
case 'megagroup':
case 'group': {
if(/* actionType === 'delete' && */appChatsManager.hasRights(-peerId, 'delete_chat')) {
if(/* actionType === 'delete' && */appChatsManager.hasRights(peerId.toChatId(), 'delete_chat')) {
title = 'DeleteMegaMenu';
description = 'AreYouSureDeleteAndExit';
buttons = [{

View File

@ -15,7 +15,7 @@ import PeerTitle from "../peerTitle";
import appPeersManager from "../../lib/appManagers/appPeersManager";
export default class PopupDeleteMessages {
constructor(peerId: number, mids: number[], type: ChatType, onConfirm?: () => void) {
constructor(peerId: PeerId, mids: number[], type: ChatType, onConfirm?: () => void) {
const peerTitleElement = new PeerTitle({peerId}).element;
mids = mids.slice();
@ -51,15 +51,15 @@ export default class PopupDeleteMessages {
if(peerId === rootScope.myId || type === 'scheduled') {
} else {
if(peerId > 0) {
if(peerId.isUser()) {
checkboxes.push({
text: 'DeleteMessagesOptionAlso',
textArgs: [peerTitleElement]
});
} else {
const chat = appChatsManager.getChat(-peerId);
const chat = appChatsManager.getChat(peerId.toChatId());
const hasRights = appChatsManager.hasRights(-peerId, 'delete_messages');
const hasRights = appChatsManager.hasRights(peerId.toChatId(), 'delete_messages');
if(chat._ === 'chat') {
const canRevoke = hasRights ? mids.slice() : mids.filter(mid => {
const message = appMessagesManager.getMessageByPeer(peerId, mid);

View File

@ -9,8 +9,8 @@ import PopupPickUser from "./pickUser";
export default class PopupForward extends PopupPickUser {
constructor(
peerIdMids: {[fromPeerId: number]: number[]},
onSelect?: (peerId: number) => Promise<void> | void,
peerIdMids: {[frompeerId: PeerId]: number[]},
onSelect?: (peerId: PeerId) => Promise<void> | void,
onClose?: () => void,
overrideOnSelect = false
) {

View File

@ -8,9 +8,11 @@ import PopupElement, { addCancelButton } from ".";
import { ChatInvite, Updates } from "../../layer";
import apiUpdatesManager from "../../lib/appManagers/apiUpdatesManager";
import appAvatarsManager from "../../lib/appManagers/appAvatarsManager";
import appChatsManager from "../../lib/appManagers/appChatsManager";
import appPhotosManager from "../../lib/appManagers/appPhotosManager";
import { i18n } from "../../lib/langPack";
import apiManager from "../../lib/mtproto/mtprotoworker";
import { NULL_PEER_ID } from "../../lib/mtproto/mtproto_config";
import RichTextProcessor from "../../lib/richtextprocessor";
import rootScope from "../../lib/rootScope";
import AvatarElement from "../avatar";
@ -27,7 +29,7 @@ export default class PopupJoinChatInvite extends PopupElement {
.then((updates) => {
apiUpdatesManager.processUpdateMessage(updates);
const chat = (updates as Updates.updates).chats[0];
const peerId = -chat.id;
const peerId = chat.id.toPeerId(true);
rootScope.dispatchEvent('history_focus', {peerId});
});
}
@ -63,7 +65,7 @@ export default class PopupJoinChatInvite extends PopupElement {
});
avatarElem.style.width = avatarElem.style.height = '';
} else {
appAvatarsManager.putPhoto(avatarElem, -0, false, chatInvite.title);
appAvatarsManager.putPhoto(avatarElem, NULL_PEER_ID, false, chatInvite.title);
}
const title = document.createElement('div');

View File

@ -14,7 +14,7 @@ export type PopupPeerButtonCallback = (checkboxes?: PopupPeerButtonCallbackCheck
export type PopupPeerCheckboxOptions = CheckboxFieldOptions & {checkboxField?: CheckboxField};
export type PopupPeerOptions = PopupOptions & Partial<{
peerId: number,
peerId: PeerId,
title: string,
titleLangKey?: LangPackKey,
titleLangArgs?: any[],

View File

@ -14,7 +14,7 @@ export default class PopupPickUser extends PopupElement {
constructor(options: {
peerTypes: AppSelectPeers['peerType'],
onSelect?: (peerId: number) => Promise<void> | void,
onSelect?: (peerId: PeerId) => Promise<void> | void,
onClose?: () => void,
placeholder: LangPackKey,
chatRightsAction?: AppSelectPeers['chatRightsAction'],
@ -29,7 +29,7 @@ export default class PopupPickUser extends PopupElement {
appendTo: this.body,
onChange: async() => {
const selected = this.selector.getSelected();
const peerId = selected[selected.length - 1];
const peerId = selected[selected.length - 1].toPeerId();
if(options.onSelect) {
const res = options.onSelect(peerId);

View File

@ -15,7 +15,7 @@ import PopupPeer from "./peer";
import PopupReportMessagesConfirm from "./reportMessagesConfirm";
export default class PopupReportMessages extends PopupPeer {
constructor(peerId: number, mids: number[], onConfirm?: () => void) {
constructor(peerId: PeerId, mids: number[], onConfirm?: () => void) {
super('popup-report-messages', {titleLangKey: 'ChatTitle.ReportMessages', buttons: [], body: true});
mids = mids.slice();

View File

@ -14,7 +14,7 @@ import PopupPeer from "./peer";
export default class PopupReportMessagesConfirm extends PopupPeer {
public static STICKER_EMOJI = '👮‍♀️';
constructor(peerId: number, mids: number[], reason: ReportReason['_'], onConfirm?: () => void) {
constructor(peerId: PeerId, mids: number[], reason: ReportReason['_'], onConfirm?: () => void) {
super('popup-report-messages-confirm', {
noTitle: true,
descriptionLangKey: 'ReportInfo',

View File

@ -9,7 +9,7 @@ import { PopupButton } from ".";
import PopupPeer from "./peer";
export default class PopupSendNow {
constructor(peerId: number, mids: number[], onConfirm?: () => void) {
constructor(peerId: PeerId, mids: number[], onConfirm?: () => void) {
let title: string, description: string, buttons: PopupButton[] = [];
title = `Send Message${mids.length > 1 ? 's' : ''} Now`;

View File

@ -14,7 +14,7 @@ import appChatsManager from "../../lib/appManagers/appChatsManager";
import PeerTitle from "../peerTitle";
export default class PopupPinMessage {
constructor(peerId: number, mid: number, unpin?: true, onConfirm?: () => void) {
constructor(peerId: PeerId, mid: number, unpin?: true, onConfirm?: () => void) {
let title: LangPackKey, description: LangPackKey, descriptionArgs: FormatterArguments,
buttons: PopupPeerOptions['buttons'] = [], checkboxes: PopupPeerOptions['checkboxes'] = [];
@ -65,13 +65,13 @@ export default class PopupPinMessage {
title = 'PinMessageAlertTitle';
const pinButtonText: LangPackKey = 'PinMessage';
if(peerId < 0) {
if(peerId.isAnyChat()) {
buttons.push({
langKey: pinButtonText,
callback: (checked) => callback(checked, false, !checked.size)
});
if(appChatsManager.isBroadcast(-peerId)) {
if(appChatsManager.isBroadcast(peerId.toChatId())) {
description = 'PinMessageAlertChannel';
} else {
description = 'PinMessageAlert';

View File

@ -6,6 +6,7 @@
import { randomLong } from "../helpers/random";
import { InputPrivacyKey, InputPrivacyRule } from "../layer";
import appChatsManager from "../lib/appManagers/appChatsManager";
import appPrivacyManager, { PrivacyType } from "../lib/appManagers/appPrivacyManager";
import appUsersManager from "../lib/appManagers/appUsersManager";
import { i18n, join, LangPackKey, _i18n } from "../lib/langPack";
@ -29,8 +30,8 @@ export default class PrivacySection {
clickable: true
}>;
public peerIds: {
disallow?: number[],
allow?: number[]
disallow?: PeerId[],
allow?: PeerId[]
};
public type: PrivacyType;
@ -152,8 +153,8 @@ export default class PrivacySection {
(['allow', 'disallow'] as ('allow' | 'disallow')[]).forEach(k => {
const arr = [];
const from = k === 'allow' ? details.allowPeers : details.disallowPeers;
arr.push(...from.users);
arr.push(...from.chats.map(id => -id));
arr.push(...from.users.map(id => id.toPeerId()));
arr.push(...from.chats.map(id => id.toPeerId(false)));
this.peerIds[k] = arr;
const s = this.exceptions.get(k).row.subtitle;
s.innerHTML = '';
@ -189,12 +190,11 @@ export default class PrivacySection {
return;
}
const _peerIds: number[] = this.peerIds[k];
const _peerIds = this.peerIds[k];
if(_peerIds) {
const splitted = this.splitPeersByType(_peerIds);
if(splitted.chats.length) {
rules.push({_: chatKey, chats: splitted.chats.map(peerId => -peerId)});
rules.push({_: chatKey, chats: splitted.chats.map(peerId => peerId.toChatId())});
}
if(splitted.users.length) {
@ -236,16 +236,16 @@ export default class PrivacySection {
row.radioField.input.checked = true;
}
private splitPeersByType(peerIds: number[]) {
const peers = {users: [] as number[], chats: [] as number[]};
private splitPeersByType(peerIds: PeerId[]) {
const peers = {users: [] as UserId[], chats: [] as ChatId[]};
peerIds.forEach(peerId => {
peers[peerId < 0 ? 'chats' : 'users'].push(peerId < 0 ? -peerId : peerId);
peers[peerId.isAnyChat() ? 'chats' : 'users'].push(peerId.isAnyChat() ? peerId.toChatId() : peerId);
});
return peers;
}
private generateStr(peers: {users: number[], chats: number[]}) {
private generateStr(peers: {users: UserId[], chats: ChatId[]}) {
if(!peers.users.length && !peers.chats.length) {
return [i18n('PrivacySettingsController.AddUsers')];
}

View File

@ -39,6 +39,7 @@ import replaceContent from "../../helpers/dom/replaceContent";
import sessionStorage from "../../lib/sessionStorage";
import { CLICK_EVENT_NAME } from "../../helpers/dom/clickEvent";
import { closeBtnMenu } from "../misc";
import { indexOfAndSplice } from "../../helpers/array";
export const LEFT_COLUMN_ACTIVE_CLASSNAME = 'is-left-column-shown';
@ -71,7 +72,6 @@ export class AppSidebarLeft extends SidebarSlider {
const onNewGroupClick = () => {
new AppAddMembersTab(this).open({
peerId: 0,
type: 'chat',
skippable: false,
takeOut: (peerIds) => {
@ -96,8 +96,8 @@ export class AppSidebarLeft extends SidebarSlider {
new AppArchivedTab(this).open();
},
verify: () => {
const folder = appMessagesManager.dialogsStorage.getFolder(1);
return !!folder.length;
const folder = appMessagesManager.dialogsStorage.getFolderDialogs(1, false);
return !!folder.length || !appMessagesManager.dialogsStorage.isDialogsLoaded(1);
}
};
@ -174,7 +174,10 @@ export class AppSidebarLeft extends SidebarSlider {
icon: 'char z',
text: 'ChatList.Menu.SwitchTo.Z',
onClick: () => {
sessionStorage.set({kz_version: 'Z'}).then(() => {
Promise.all([
sessionStorage.set({kz_version: 'Z'}),
sessionStorage.delete('tgme_sync')
]).then(() => {
location.href = 'https://web.telegram.org/z/';
});
},
@ -183,7 +186,9 @@ export class AppSidebarLeft extends SidebarSlider {
icon: 'char w',
text: 'ChatList.Menu.SwitchTo.Webogram',
onClick: () => {
location.href = 'https://web.telegram.org/?legacy=1';
sessionStorage.delete('tgme_sync').then(() => {
location.href = 'https://web.telegram.org/?legacy=1';
});
},
verify: () => App.isMainDomain
}];
@ -250,9 +255,13 @@ export class AppSidebarLeft extends SidebarSlider {
btnArchive.element.append(this.archivedCount);
rootScope.addEventListener('dialogs_archived_unread', (e) => {
this.archivedCount.innerText = '' + formatNumber(e.count, 1);
this.archivedCount.classList.toggle('hide', !e.count);
rootScope.addEventListener('folder_unread', (folder) => {
if(folder.id === 1) {
// const count = folder.unreadMessagesCount;
const count = folder.unreadDialogsCount;
this.archivedCount.innerText = '' + formatNumber(count, 1);
this.archivedCount.classList.toggle('hide', !count);
}
});
appUsersManager.getTopPeers('correspondents');
@ -322,7 +331,7 @@ export class AppSidebarLeft extends SidebarSlider {
const resetSearch = () => {
searchSuper.setQuery({
peerId: 0,
peerId: ''.toPeerId(),
folderId: 0
});
searchSuper.selectTab(0);
@ -332,7 +341,7 @@ export class AppSidebarLeft extends SidebarSlider {
resetSearch();
let pickedElements: HTMLElement[] = [];
let selectedPeerId = 0;
let selectedPeerId: PeerId = ''.toPeerId();
let selectedMinDate = 0;
let selectedMaxDate = 0;
const updatePicked = () => {
@ -361,7 +370,7 @@ export class AppSidebarLeft extends SidebarSlider {
selectedMinDate = +minDate;
selectedMaxDate = +maxDate;
} else {
selectedPeerId = +key;
selectedPeerId = key.toPeerId();
}
target.addEventListener('click', () => {
@ -376,7 +385,7 @@ export class AppSidebarLeft extends SidebarSlider {
searchSuper.nav.parentElement.append(helper);
const renderEntity = (peerId: any, title?: string | HTMLElement) => {
const renderEntity = (key: PeerId | string, title?: string | HTMLElement) => {
const div = document.createElement('div');
div.classList.add('selector-user'/* , 'scale-in' */);
@ -385,13 +394,13 @@ export class AppSidebarLeft extends SidebarSlider {
avatarEl.setAttribute('dialog', '1');
avatarEl.classList.add('avatar-30');
div.dataset.key = '' + peerId;
if(typeof(peerId) === 'number') {
div.dataset.key = '' + key;
if(key.isPeerId()) {
if(title === undefined) {
title = new PeerTitle({peerId}).element;
title = new PeerTitle({peerId: key.toPeerId()}).element;
}
avatarEl.setAttribute('peer', '' + peerId);
avatarEl.setAttribute('peer', '' + key);
} else {
avatarEl.classList.add('tgico-calendarfilter');
}
@ -415,11 +424,11 @@ export class AppSidebarLeft extends SidebarSlider {
if(key.indexOf('date_') === 0) {
selectedMinDate = selectedMaxDate = 0;
} else {
selectedPeerId = 0;
selectedPeerId = ''.toPeerId();
}
target.remove();
pickedElements.findAndSplice(t => t === target);
indexOfAndSplice(pickedElements, target);
setTimeout(() => {
updatePicked();
@ -452,8 +461,9 @@ export class AppSidebarLeft extends SidebarSlider {
if(!selectedPeerId && value.trim()) {
const middleware = searchSuper.middleware.get();
Promise.all([
appMessagesManager.getConversationsAll(value).then(dialogs => dialogs.map(d => d.peerId)),
appUsersManager.getContacts(value, true)
// appMessagesManager.getConversationsAll(value).then(dialogs => dialogs.map(d => d.peerId)),
appMessagesManager.getConversations(value).promise.then(({dialogs}) => dialogs.map(d => d.peerId)),
appUsersManager.getContactsPeerIds(value, true)
]).then(results => {
if(!middleware()) return;
const peerIds = new Set(results[0].concat(results[1]));
@ -489,11 +499,11 @@ export class AppSidebarLeft extends SidebarSlider {
return;
}
const peerId = +target.getAttribute('data-peer-id');
const peerId = target.getAttribute('data-peer-id').toPeerId();
appStateManager.getState().then(state => {
const recentSearch = state.recentSearch || [];
if(recentSearch[0] !== peerId) {
recentSearch.findAndSplice(p => p === peerId);
indexOfAndSplice(recentSearch, peerId);
recentSearch.unshift(peerId);
if(recentSearch.length > 20) {
recentSearch.length = 20;

View File

@ -38,7 +38,7 @@ export default class AppActiveSessionsTab extends SliderSuperTab {
titleRight: auth.pFlags.current ? undefined : formatDateAccordingToTodayNew(new Date(Math.max(auth.date_active, auth.date_created) * 1000))
});
row.container.dataset.hash = auth.hash;
row.container.dataset.hash = '' + auth.hash;
const midtitle = document.createElement('div');
midtitle.classList.add('row-midtitle');

View File

@ -14,7 +14,7 @@ export default class AppAddMembersTab extends SliderSuperTab {
private nextBtn: HTMLButtonElement;
private selector: AppSelectPeers;
private peerType: 'channel' | 'chat' | 'privacy';
private takeOut: (peerIds: number[]) => Promise<any> | false | void;
private takeOut: (peerIds: PeerId[]) => Promise<any> | false | void;
private skippable: boolean;
protected init() {
@ -23,7 +23,7 @@ export default class AppAddMembersTab extends SliderSuperTab {
this.scrollable.container.remove();
this.nextBtn.addEventListener('click', () => {
const peerIds = this.selector.getSelected();
const peerIds = this.selector.getSelected().map(sel => sel.toPeerId());
if(this.skippable) {
this.takeOut(peerIds);
@ -53,11 +53,10 @@ export default class AppAddMembersTab extends SliderSuperTab {
public open(options: {
title: LangPackKey,
placeholder: LangPackKey,
peerId?: number,
type: AppAddMembersTab['peerType'],
takeOut?: AppAddMembersTab['takeOut'],
skippable: boolean,
selectedPeerIds?: number[]
selectedPeerIds?: PeerId[]
}) {
const ret = super.open();

View File

@ -35,7 +35,7 @@ export default class AppBackgroundTab extends SliderSuperTab {
private grid: HTMLElement;
private tempId = 0;
private theme: Theme;
private clicked: Set<string> = new Set();
private clicked: Set<DocId> = new Set();
private blurCheckboxField: CheckboxField;
init() {
@ -162,7 +162,7 @@ export default class AppBackgroundTab extends SliderSuperTab {
wallpaper = _wallpaper as WallPaper.wallPaper;
wallpaper.document = appDocsManager.saveDoc(wallpaper.document);
container.dataset.docId = wallpaper.document.id;
container.dataset.docId = '' + wallpaper.document.id;
container.dataset.slug = wallpaper.slug;
this.setBackgroundDocument(wallpaper.slug, wallpaper.document).then(deferred.resolve, deferred.reject);
@ -223,7 +223,7 @@ export default class AppBackgroundTab extends SliderSuperTab {
size: appPhotosManager.choosePhotoSize(wallpaper.document, 200, 200)
});
container.dataset.docId = wallpaper.document.id;
container.dataset.docId = '' + wallpaper.document.id;
container.dataset.slug = wallpaper.slug;
if(this.theme.background.type === 'image' && this.theme.background.slug === wallpaper.slug) {

View File

@ -17,7 +17,7 @@ import ButtonCorner from "../../buttonCorner";
import { attachClickEvent } from "../../../helpers/dom/clickEvent";
export default class AppBlockedUsersTab extends SliderSuperTab {
public peerIds: number[];
public peerIds: PeerId[];
private menuElement: HTMLElement;
protected init() {
@ -50,7 +50,7 @@ export default class AppBlockedUsersTab extends SliderSuperTab {
this.scrollable.container.classList.add('chatlist-container');
this.scrollable.append(list);
const add = (peerId: number, append: boolean) => {
const add = (peerId: PeerId, append: boolean) => {
const {dom} = appDialogsManager.addDialogNew({
dialog: peerId,
container: list,
@ -78,7 +78,7 @@ export default class AppBlockedUsersTab extends SliderSuperTab {
let target: HTMLElement;
const onUnblock = () => {
const peerId = +target.dataset.peerId;
const peerId = target.dataset.peerId.toPeerId();
appUsersManager.toggleBlock(peerId, false);
};

View File

@ -11,7 +11,6 @@ import { toast } from "../../toast";
import type { MyDialogFilter } from "../../../lib/storages/filters";
import type { DialogFilterSuggested, DialogFilter } from "../../../layer";
import type _rootScope from "../../../lib/rootScope";
import type { BroadcastEvents } from "../../../lib/rootScope";
import Button from "../../button";
import appMessagesManager from "../../../lib/appManagers/appMessagesManager";
import appPeersManager from "../../../lib/appManagers/appPeersManager";
@ -65,7 +64,7 @@ export default class AppChatFoldersTab extends SliderSuperTab {
}
if(!d.length) {
const folder = appMessagesManager.dialogsStorage.getFolder(filter.id);
const folder = appMessagesManager.dialogsStorage.getFolderDialogs(filter.id);
let chats = 0, channels = 0, groups = 0;
for(const dialog of folder) {
if(appPeersManager.isAnyGroup(dialog.peerId)) groups++;
@ -170,8 +169,7 @@ export default class AppChatFoldersTab extends SliderSuperTab {
onFiltersContainerUpdate();
});
this.listenerSetter.add(rootScope)('filter_update', (e) => {
const filter = e;
this.listenerSetter.add(rootScope)('filter_update', (filter) => {
if(this.filtersRendered.hasOwnProperty(filter.id)) {
this.renderFolder(filter, null, this.filtersRendered[filter.id]);
} else {
@ -183,8 +181,7 @@ export default class AppChatFoldersTab extends SliderSuperTab {
this.getSuggestedFilters();
});
this.listenerSetter.add(rootScope)('filter_delete', (e) => {
const filter = e;
this.listenerSetter.add(rootScope)('filter_delete', (filter) => {
if(this.filtersRendered.hasOwnProperty(filter.id)) {
/* for(const suggested of this.suggestedFilters) {
if(deepEqual(suggested.filter, filter)) {
@ -200,8 +197,7 @@ export default class AppChatFoldersTab extends SliderSuperTab {
onFiltersContainerUpdate();
});
this.listenerSetter.add(rootScope)('filter_order', (e: BroadcastEvents['filter_order']) => {
const order = e;
this.listenerSetter.add(rootScope)('filter_order', (order) => {
order.forEach((filterId, idx) => {
const container = this.filtersRendered[filterId].container;
positionElementByIndex(container, container.parentElement, idx + 1); // ! + 1 due to header

View File

@ -44,8 +44,9 @@ export default class AppContactsTab extends SliderSuperTab {
this.listenerSetter.add(rootScope)('contacts_update', (userId) => {
const isContact = appUsersManager.isContact(userId);
if(isContact) this.sortedUserList.add(userId);
else this.sortedUserList.delete(userId);
const peerId = userId.toPeerId();
if(isContact) this.sortedUserList.add(peerId);
else this.sortedUserList.delete(peerId);
});
this.title.replaceWith(this.inputSearch.container);
@ -90,7 +91,7 @@ export default class AppContactsTab extends SliderSuperTab {
this.scrollable.onScrolledBottom = null;
this.scrollable.container.textContent = '';
appUsersManager.getContacts(query, undefined, 'online').then(contacts => {
appUsersManager.getContactsPeerIds(query, undefined, 'online').then(contacts => {
if(!middleware()) {
return;
}

View File

@ -31,8 +31,8 @@ export default class AppEditFolderTab extends SliderSuperTab {
private menuBtn: HTMLElement;
private nameInputField: InputField;
private include_peers: SettingSection;
private exclude_peers: SettingSection;
private includePeerIds: SettingSection;
private excludePeerIds: SettingSection;
private flags: {[k in 'contacts' | 'non_contacts' | 'groups' | 'broadcasts' | 'bots' | 'exclude_muted' | 'exclude_archived' | 'exclude_read']: HTMLElement} = {} as any;
private animation: RLottiePlayer;
@ -108,7 +108,7 @@ export default class AppEditFolderTab extends SliderSuperTab {
return section;
};
this.include_peers = generateList('folder-list-included', 'FilterInclude', [{
this.includePeerIds = generateList('folder-list-included', 'FilterInclude', [{
icon: 'add primary',
text: 'ChatList.Filter.Include.AddChat',
withRipple: true
@ -134,7 +134,7 @@ export default class AppEditFolderTab extends SliderSuperTab {
name: 'bots'
}], this.flags);
this.exclude_peers = generateList('folder-list-excluded', 'FilterExclude', [{
this.excludePeerIds = generateList('folder-list-excluded', 'FilterExclude', [{
icon: 'minus primary',
text: 'ChatList.Filter.Exclude.AddChat',
withRipple: true
@ -152,10 +152,10 @@ export default class AppEditFolderTab extends SliderSuperTab {
name: 'exclude_read'
}], this.flags);
this.scrollable.append(this.stickerContainer, this.caption, inputWrapper, this.include_peers.container, this.exclude_peers.container);
this.scrollable.append(this.stickerContainer, this.caption, inputWrapper, this.includePeerIds.container, this.excludePeerIds.container);
const includedFlagsContainer = this.include_peers.container.querySelector('.folder-categories');
const excludedFlagsContainer = this.exclude_peers.container.querySelector('.folder-categories');
const includedFlagsContainer = this.includePeerIds.container.querySelector('.folder-categories');
const excludedFlagsContainer = this.excludePeerIds.container.querySelector('.folder-categories');
includedFlagsContainer.querySelector('.btn').addEventListener('click', () => {
new AppIncludedChatsTab(this.slider).open(this.filter, 'included', this);
@ -261,7 +261,7 @@ export default class AppEditFolderTab extends SliderSuperTab {
this.flags[flag as keyof AppEditFolderTab['flags']].style.display = !!filter.pFlags[flag as keyof AppEditFolderTab['flags']] ? '' : 'none';
}
(['include_peers', 'exclude_peers'] as ['include_peers', 'exclude_peers']).forEach(key => {
(['includePeerIds' as const, 'excludePeerIds' as const]).forEach(key => {
const section = this[key];
const ul = appDialogsManager.createChatList();
@ -339,7 +339,10 @@ export default class AppEditFolderTab extends SliderSuperTab {
pFlags: {},
pinned_peers: [],
include_peers: [],
exclude_peers: []
exclude_peers: [],
pinnedPeerIds: [],
includePeerIds: [],
excludePeerIds: []
}, true);
this.type = 'create';
this.onCreateOpen();

View File

@ -12,6 +12,7 @@ import EditPeer from "../../editPeer";
import { UsernameInputField } from "../../usernameInputField";
import { i18n, i18n_ } from "../../../lib/langPack";
import { attachClickEvent } from "../../../helpers/dom/clickEvent";
import rootScope from "../../../lib/rootScope";
// TODO: аватарка не поменяется в этой вкладке после изменения почему-то (если поставить в другом клиенте, и потом тут проверить, для этого ещё вышел в чатлист)
@ -65,7 +66,7 @@ export default class AppEditProfileTab extends SliderSuperTab {
this.scrollable.append(document.createElement('hr'));
this.editPeer = new EditPeer({
peerId: appUsersManager.getSelf().id,
peerId: rootScope.myId,
inputFields,
listenerSetter: this.listenerSetter
});
@ -81,7 +82,6 @@ export default class AppEditProfileTab extends SliderSuperTab {
inputWrapper.classList.add('input-wrapper');
this.usernameInputField = new UsernameInputField({
peerId: 0,
label: 'EditProfile.Username.Label',
name: 'username',
plainText: true,

View File

@ -19,6 +19,8 @@ import appMessagesManager from "../../../lib/appManagers/appMessagesManager";
import RichTextProcessor from "../../../lib/richtextprocessor";
import { SettingSection } from "..";
import { toast } from "../../toast";
import { forEachReverse } from "../../../helpers/array";
import appPeersManager from "../../../lib/appManagers/appPeersManager";
export default class AppIncludedChatsTab extends SliderSuperTab {
private editFolderTab: AppEditFolderTab;
@ -29,7 +31,7 @@ export default class AppIncludedChatsTab extends SliderSuperTab {
private filter: DialogFilter;
private originalFilter: DialogFilter;
private dialogsByFilters: Map<DialogFilter, Set<number>>;
private dialogsByFilters: Map<DialogFilter, Set<PeerId>>;
protected init() {
this.content.remove();
@ -64,39 +66,41 @@ export default class AppIncludedChatsTab extends SliderSuperTab {
}
}
const peers: number[] = [];
const peerIds: PeerId[] = [];
for(const key of selected) {
if(typeof(key) === 'number') {
peers.push(key);
if(key.isPeerId()) {
peerIds.push(key.toPeerId());
} else {
// @ts-ignore
this.filter.pFlags[key] = true;
}
}
let cmp: (peerId: PeerId) => boolean;
if(this.type === 'included') {
this.filter.pinned_peers = this.filter.pinned_peers.filter(peerId => {
return peers.includes(peerId); // * because I have pinned peer in include_peers too
/* const index = peers.indexOf(peerId);
if(index !== -1) {
peers.splice(index, 1);
return true;
} else {
return false;
} */
});
cmp = (peerId) => peerIds.includes(peerId);
} else {
this.filter.pinned_peers = this.filter.pinned_peers.filter(peerId => {
return !peers.includes(peerId);
});
cmp = (peerId) => !peerIds.includes(peerId);
}
const other = this.type === 'included' ? 'exclude_peers' : 'include_peers';
this.filter[other] = this.filter[other].filter(peerId => {
return !peers.includes(peerId);
forEachReverse(this.filter.pinnedPeerIds, (peerId, idx) => {
if(!cmp(peerId)) {
this.filter.pinnedPeerIds.splice(idx, 1);
this.filter.pinned_peers.splice(idx, 1);
}
});
const other = this.type === 'included' ? 'excludePeerIds' : 'includePeerIds';
const otherLegacy = this.type === 'included' ? 'exclude_peers' : 'include_peers';
forEachReverse(this.filter[other], (peerId, idx) => {
if(peerIds.includes(peerId)) {
this.filter[other].splice(idx, 1);
this.filter[otherLegacy].splice(idx, 1);
}
});
this.filter[this.type === 'included' ? 'include_peers' : 'exclude_peers'] = peers;
this.filter[this.type === 'included' ? 'includePeerIds' : 'excludePeerIds'] = peerIds;
this.filter[this.type === 'included' ? 'include_peers' : 'exclude_peers'] = peerIds.map(peerId => appPeersManager.getInputPeerById(peerId));
//this.filter.pinned_peers = this.filter.pinned_peers.filter(peerId => this.filter.include_peers.includes(peerId));
this.editFolderTab.setFilter(this.filter, false);
@ -106,7 +110,7 @@ export default class AppIncludedChatsTab extends SliderSuperTab {
this.dialogsByFilters = new Map();
return appMessagesManager.filtersStorage.getDialogFilters().then(filters => {
for(const filter of filters) {
this.dialogsByFilters.set(filter, new Set(appMessagesManager.dialogsStorage.getFolder(filter.id).map(d => d.peerId)));
this.dialogsByFilters.set(filter, new Set(appMessagesManager.dialogsStorage.getFolderDialogs(filter.id).map(d => d.peerId)));
}
});
}
@ -122,7 +126,7 @@ export default class AppIncludedChatsTab extends SliderSuperTab {
return checkboxField.label;
}
renderResults = async(peerIds: number[]) => {
renderResults = async(peerIds: PeerId[]) => {
//const other = this.type === 'included' ? this.filter.exclude_peers : this.filter.include_peers;
await appUsersManager.getContacts();
@ -211,7 +215,7 @@ export default class AppIncludedChatsTab extends SliderSuperTab {
/////////////////
const selectedPeers = (this.type === 'included' ? filter.include_peers : filter.exclude_peers).slice();
const selectedPeers = (this.type === 'included' ? filter.includePeerIds : filter.excludePeerIds).slice();
this.selector = new AppSelectPeers({
appendTo: this.container,

View File

@ -75,13 +75,12 @@ export default class AppNewChannelTab extends SliderSuperTab {
appSidebarLeft.removeTabFromHistory(this);
new AppAddMembersTab(this.slider).open({
peerId: channelId,
type: 'channel',
skippable: true,
title: 'GroupAddMembers',
placeholder: 'SendMessageTo',
takeOut: (peerIds) => {
return appChatsManager.inviteToChannel(Math.abs(channelId), peerIds);
return appChatsManager.inviteToChannel(channelId, peerIds);
}
});
});

View File

@ -20,7 +20,7 @@ export default class AppNewGroupTab extends SliderSuperTab {
private searchGroup = new SearchGroup(true, 'contacts', true, 'new-group-members disable-hover', false);
private avatarEdit: AvatarEdit;
private uploadAvatar: () => Promise<InputFile> = null;
private userIds: number[];
private peerIds: PeerId[];
private nextBtn: HTMLButtonElement;
private groupNameInputField: InputField;
@ -53,7 +53,7 @@ export default class AppNewGroupTab extends SliderSuperTab {
const title = this.groupNameInputField.value;
this.nextBtn.disabled = true;
appChatsManager.createChat(title, this.userIds).then((chatId) => {
appChatsManager.createChat(title, this.peerIds).then((chatId) => {
if(this.uploadAvatar) {
this.uploadAvatar().then((inputFile) => {
appChatsManager.editPhoto(chatId, inputFile);
@ -81,12 +81,12 @@ export default class AppNewGroupTab extends SliderSuperTab {
this.nextBtn.disabled = false;
}
public open(userIds: number[]) {
public open(userIds: PeerId[]) {
const result = super.open();
result.then(() => {
this.userIds = userIds;
this.peerIds = userIds;
this.userIds.forEach(userId => {
this.peerIds.forEach(userId => {
let {dom} = appDialogsManager.addDialogNew({
dialog: userId,
container: this.searchGroup.list,
@ -99,7 +99,7 @@ export default class AppNewGroupTab extends SliderSuperTab {
});
this.searchGroup.nameEl.textContent = '';
this.searchGroup.nameEl.append(i18n('Members', [this.userIds.length]));
this.searchGroup.nameEl.append(i18n('Members', [this.peerIds.length]));
this.searchGroup.setActive();
});

View File

@ -46,7 +46,7 @@ export default class AppPrivacyAndSecurityTab extends SliderSuperTabEventable {
{
const section = new SettingSection({noDelimiter: true});
let blockedPeerIds: number[];
let blockedPeerIds: PeerId[];
const blockedUsersRow = new Row({
icon: 'deleteuser',
titleLangKey: 'BlockedUsers',

View File

@ -18,6 +18,7 @@ import AppNotificationsTab from "./notifications";
import PeerTitle from "../../peerTitle";
import AppLanguageTab from "./language";
import lottieLoader from "../../../lib/lottieLoader";
import PopupPeer from "../../popups/peer";
//import AppMediaViewer from "../../appMediaViewerNew";
export default class AppSettingsTab extends SliderSuperTab {
@ -42,7 +43,17 @@ export default class AppSettingsTab extends SliderSuperTab {
icon: 'logout',
text: 'EditAccount.Logout',
onClick: () => {
apiManager.logOut();
new PopupPeer('logout', {
titleLangKey: 'LogOut',
descriptionLangKey: 'LogOut.Description',
buttons: [{
langKey: 'LogOut',
callback: () => {
apiManager.logOut();
},
isDanger: true
}]
}).show();
}
}]);
@ -149,10 +160,11 @@ export default class AppSettingsTab extends SliderSuperTab {
}
public fillElements() {
let user = appUsersManager.getSelf();
this.avatarElem.setAttribute('peer', '' + user.id);
const user = appUsersManager.getSelf();
const peerId = user.id.toPeerId(false);
this.avatarElem.setAttribute('peer', '' + peerId);
this.nameDiv.append(new PeerTitle({peerId: user.id}).element);
this.nameDiv.append(new PeerTitle({peerId: peerId}).element);
this.phoneDiv.innerHTML = user.phone ? appUsersManager.formatUserPhone(user.phone) : '';
}
}

View File

@ -24,7 +24,7 @@ import { attachClickEvent } from "../../../helpers/dom/clickEvent";
import toggleDisability from "../../../helpers/dom/toggleDisability";
export default class AppChatTypeTab extends SliderSuperTabEventable {
public chatId: number;
public chatId: ChatId;
public chatFull: ChatFull;
protected init() {
@ -130,7 +130,7 @@ export default class AppChatTypeTab extends SliderSuperTabEventable {
invalidText: 'Link.Invalid',
takenText: 'Link.Taken',
onChange: onChange,
peerId: -this.chatId,
peerId: this.chatId.toPeerId(true),
head: placeholder
});

View File

@ -18,7 +18,6 @@ import rootScope from "../../../lib/rootScope";
import AppGroupPermissionsTab from "./groupPermissions";
import { i18n, LangPackKey } from "../../../lib/langPack";
import PopupDeleteDialog from "../../popups/deleteDialog";
import PopupPeer from "../../popups/peer";
import { attachClickEvent } from "../../../helpers/dom/clickEvent";
import toggleDisability from "../../../helpers/dom/toggleDisability";
import CheckboxField from "../../checkboxField";
@ -27,7 +26,7 @@ export default class AppEditChatTab extends SliderSuperTab {
private chatNameInputField: InputField;
private descriptionInputField: InputField;
private editPeer: EditPeer;
public chatId: number;
public chatId: ChatId;
protected async _init() {
// * cleanup prev
@ -54,6 +53,8 @@ export default class AppEditChatTab extends SliderSuperTab {
}
});
const peerId = this.chatId.toPeerId(true);
{
const section = new SettingSection({noDelimiter: true});
const inputFields: InputField[] = [];
@ -81,7 +82,7 @@ export default class AppEditChatTab extends SliderSuperTab {
inputFields.push(this.chatNameInputField, this.descriptionInputField);
this.editPeer = new EditPeer({
peerId: -this.chatId,
peerId,
inputFields,
listenerSetter: this.listenerSetter
});
@ -283,7 +284,7 @@ export default class AppEditChatTab extends SliderSuperTab {
const btnDelete = Button('btn-primary btn-transparent danger', {icon: 'delete', text: isBroadcast ? 'PeerInfo.DeleteChannel' : 'DeleteAndExitButton'});
attachClickEvent(btnDelete, () => {
new PopupDeleteDialog(-this.chatId/* , 'delete' */, undefined, (promise) => {
new PopupDeleteDialog(peerId/* , 'delete' */, undefined, (promise) => {
const toggle = toggleDisability([btnDelete], true);
promise.then(() => {
this.close();
@ -301,8 +302,8 @@ export default class AppEditChatTab extends SliderSuperTab {
if(!isChannel) {
// ! this one will fire earlier than tab's closeAfterTimeout (destroy) event and listeners will be erased, so destroy won't fire
this.listenerSetter.add(rootScope)('dialog_migrate', ({migrateFrom, migrateTo}) => {
if(-this.chatId === migrateFrom) {
this.chatId = -migrateTo;
if(peerId === migrateFrom) {
this.chatId = migrateTo.toChatId();
this._init();
}
});

View File

@ -27,7 +27,7 @@ export default class AppEditContactTab extends SliderSuperTab {
private nameInputField: InputField;
private lastNameInputField: InputField;
private editPeer: EditPeer;
public peerId: number;
public peerId: PeerId;
protected init() {
this.container.classList.add('edit-peer-container', 'edit-contact-container');

View File

@ -6,6 +6,7 @@
import appSidebarRight from "..";
import appMessagesManager from "../../../lib/appManagers/appMessagesManager";
import { NULL_PEER_ID } from "../../../lib/mtproto/mtproto_config";
import AppSelectPeers from "../../appSelectPeers";
import { putPreloader } from "../../misc";
import { SliderTab } from "../../slider";
@ -41,7 +42,7 @@ export default class AppForwardTab implements SliderTab {
this.sendBtn = this.container.querySelector('.btn-circle') as HTMLButtonElement;
this.sendBtn.addEventListener('click', () => {
let peerIds = this.selector.getSelected();
let peerIds = this.selector.getSelected().map(s => s.toPeerId());
if(this.mids.length && peerIds.length) {
this.sendBtn.classList.remove('tgico-send');
@ -51,7 +52,7 @@ export default class AppForwardTab implements SliderTab {
let s = () => {
let promises = peerIds.splice(0, 3).map(peerId => {
return appMessagesManager.forwardMessages(peerId, 0, this.mids);
return appMessagesManager.forwardMessages(peerId, NULL_PEER_ID, this.mids);
});
Promise.all(promises).then(() => {

View File

@ -16,6 +16,7 @@ import type { MyDocument } from "../../../lib/appManagers/appDocsManager";
import mediaSizes from "../../../helpers/mediaSizes";
import findUpClassName from "../../../helpers/dom/findUpClassName";
import { attachClickEvent } from "../../../helpers/dom/clickEvent";
import { NULL_PEER_ID } from "../../../lib/mtproto/mtproto_config";
const ANIMATIONGROUP = 'GIFS-SEARCH';
@ -26,7 +27,7 @@ export default class AppGifsTab extends SliderSuperTab {
private nextOffset = '';
private loadedAll = false;
private gifBotPeerId: number;
private gifBotPeerId: PeerId;
private masonry: GifsMasonry;
private searchPromise: ReturnType<AppInlineBotsManager['getInlineResults']>;
@ -100,11 +101,11 @@ export default class AppGifsTab extends SliderSuperTab {
if(this.searchPromise || this.loadedAll) return;
if(!this.gifBotPeerId) {
this.gifBotPeerId = (await appUsersManager.resolveUsername('gif')).id;
this.gifBotPeerId = (await appUsersManager.resolveUsername('gif')).id.toPeerId(false);
}
try {
this.searchPromise = appInlineBotsManager.getInlineResults(0, this.gifBotPeerId, query, this.nextOffset);
this.searchPromise = appInlineBotsManager.getInlineResults(NULL_PEER_ID, this.gifBotPeerId, query, this.nextOffset);
const { results, next_offset } = await this.searchPromise;
if(this.inputSearch.value !== query) {

View File

@ -34,7 +34,7 @@ export class ChatPermissions {
private toggleWith: Partial<{[chatRight in ChatRights]: ChatRights[]}>;
constructor(options: {
chatId: number,
chatId: ChatId,
listenerSetter: ListenerSetter,
appendTo: HTMLElement,
participant?: ChannelParticipant.channelParticipantBanned
@ -123,7 +123,7 @@ export class ChatPermissions {
}
export default class AppGroupPermissionsTab extends SliderSuperTabEventable {
public chatId: number;
public chatId: ChatId;
protected async init() {
this.container.classList.add('edit-peer-container', 'group-permissions-container');
@ -171,7 +171,7 @@ export default class AppGroupPermissionsTab extends SliderSuperTabEventable {
}
});
const openPermissions = async(peerId: number) => {
const openPermissions = async(peerId: PeerId) => {
let participant: AppUserPermissionsTab['participant'];
try {
participant = await appProfileManager.getChannelParticipant(this.chatId, peerId) as any;
@ -208,7 +208,7 @@ export default class AppGroupPermissionsTab extends SliderSuperTabEventable {
const target = findUpTag(e.target, 'LI');
if(!target) return;
const peerId = +target.dataset.peerId;
const peerId = target.dataset.peerId.toPeerId();
openPermissions(peerId);
}, {listenerSetter: this.listenerSetter});

View File

@ -77,7 +77,7 @@ export default class AppPollResultsTab extends SliderSuperTab {
appPollsManager.getVotes(message, answer.option, offset, limit).then(votesList => {
votesList.votes.forEach(vote => {
const {dom} = appDialogsManager.addDialogNew({
dialog: vote.user_id,
dialog: vote.user_id.toPeerId(false),
container: list,
drawStatus: false,
rippleEnabled: false,

View File

@ -17,7 +17,7 @@ export default class AppPrivateSearchTab extends SliderSuperTab {
private appSearch: AppSearch;
private btnPickDate: HTMLElement;
private peerId = 0;
private peerId: PeerId;
private threadId = 0;
private query = '';
private onDatePick: (timestamp: number) => void;
@ -43,7 +43,7 @@ export default class AppPrivateSearchTab extends SliderSuperTab {
});
}
open(peerId: number, threadId?: number, onDatePick?: AppPrivateSearchTab['onDatePick'], query?: string) {
open(peerId: PeerId, threadId?: number, onDatePick?: AppPrivateSearchTab['onDatePick'], query?: string) {
const ret = super.open();
if(!this.peerId) {

View File

@ -29,12 +29,12 @@ import PeerProfile from "../../peerProfile";
export default class AppSharedMediaTab extends SliderSuperTab {
private editBtn: HTMLElement;
private peerId = 0;
private peerId: PeerId;
private threadId = 0;
private historiesStorage: {
[peerId: number]: Partial<{
[type in SearchSuperType]: {mid: number, peerId: number}[]
[peerId: PeerId]: Partial<{
[type in SearchSuperType]: {mid: number, peerId: PeerId}[]
}>
} = {};
@ -124,7 +124,7 @@ export default class AppSharedMediaTab extends SliderSuperTab {
attachClickEvent(this.editBtn, (e) => {
let tab: AppEditChatTab | AppEditContactTab;
if(this.peerId < 0) {
if(this.peerId.isAnyChat()) {
tab = new AppEditChatTab(this.slider);
} else {
tab = new AppEditContactTab(this.slider);
@ -132,7 +132,7 @@ export default class AppSharedMediaTab extends SliderSuperTab {
if(tab) {
if(tab instanceof AppEditChatTab) {
tab.chatId = -this.peerId;
tab.chatId = this.peerId.toChatId();
} else {
tab.peerId = this.peerId;
}
@ -148,14 +148,14 @@ export default class AppSharedMediaTab extends SliderSuperTab {
});
rootScope.addEventListener('chat_update', (chatId) => {
if(this.peerId === -chatId) {
if(this.peerId === chatId.toPeerId(true)) {
this.toggleEditBtn();
}
});
rootScope.addEventListener('history_multiappend', (msgIdsByPeer) => {
for(const peerId in msgIdsByPeer) {
this.renderNewMessages(+peerId, Array.from(msgIdsByPeer[peerId]));
this.renderNewMessages(peerId.toPeerId(), Array.from(msgIdsByPeer[peerId]));
}
});
@ -211,10 +211,11 @@ export default class AppSharedMediaTab extends SliderSuperTab {
this.content.append(btnAddMembers);
btnAddMembers.addEventListener('click', () => {
const id = -this.peerId;
const peerId = this.peerId;
const id = this.peerId.toChatId();
const isChannel = appChatsManager.isChannel(id);
const showConfirmation = (peerIds: number[], callback: (checked: PopupPeerButtonCallbackCheckboxes) => void) => {
const showConfirmation = (peerIds: PeerId[], callback: (checked: PopupPeerButtonCallbackCheckboxes) => void) => {
let titleLangKey: LangPackKey, titleLangArgs: any[],
descriptionLangKey: LangPackKey, descriptionLangArgs: any[],
checkboxes: PopupPeerCheckboxOptions[];
@ -254,11 +255,11 @@ export default class AppSharedMediaTab extends SliderSuperTab {
}
descriptionLangArgs.push(new PeerTitle({
peerId: -id
peerId
}).element);
new PopupPeer('popup-add-members', {
peerId: -id,
peerId,
titleLangKey,
descriptionLangKey,
descriptionLangArgs,
@ -279,7 +280,6 @@ export default class AppSharedMediaTab extends SliderSuperTab {
if(isChannel) {
const tab = new AppAddMembersTab(this.slider);
tab.open({
peerId: this.peerId,
type: 'channel',
skippable: false,
takeOut: (peerIds) => {
@ -313,7 +313,7 @@ export default class AppSharedMediaTab extends SliderSuperTab {
//console.log('construct shared media time:', performance.now() - perf);
}
public renderNewMessages(peerId: number, mids: number[]) {
public renderNewMessages(peerId: PeerId, mids: number[]) {
if(this.init) return; // * not inited yet
if(!this.historiesStorage[peerId]) return;
@ -336,7 +336,7 @@ export default class AppSharedMediaTab extends SliderSuperTab {
}
}
public deleteDeletedMessages(peerId: number, mids: number[]) {
public deleteDeletedMessages(peerId: PeerId, mids: number[]) {
if(this.init) return; // * not inited yet
if(!this.historiesStorage[peerId]) return;
@ -384,7 +384,7 @@ export default class AppSharedMediaTab extends SliderSuperTab {
this.searchSuper.cleanupHTML(true);
this.container.classList.toggle('can-add-members', this.searchSuper.canViewMembers() && appChatsManager.hasRights(-this.peerId, 'invite_users'));
this.container.classList.toggle('can-add-members', this.searchSuper.canViewMembers() && appChatsManager.hasRights(this.peerId.toChatId(), 'invite_users'));
// console.log('cleanupHTML shared media time:', performance.now() - perf);
}
@ -393,7 +393,7 @@ export default class AppSharedMediaTab extends SliderSuperTab {
this.searchSuper.loadMutex = promise;
}
public setPeer(peerId: number, threadId = 0) {
public setPeer(peerId: PeerId, threadId = 0) {
if(this.peerId === peerId && this.threadId === threadId) return false;
this.peerId = peerId;
@ -432,10 +432,10 @@ export default class AppSharedMediaTab extends SliderSuperTab {
private toggleEditBtn() {
let show: boolean;
if(this.peerId > 0) {
if(this.peerId.isUser()) {
show = this.peerId !== rootScope.myId && appUsersManager.isContact(this.peerId);
} else {
show = appChatsManager.hasRights(-this.peerId, 'change_info');
show = appChatsManager.hasRights(this.peerId.toChatId(), 'change_info');
}
this.editBtn.classList.toggle('hide', !show);

View File

@ -181,8 +181,8 @@ export default class AppStickersTab extends SliderSuperTab {
div.addEventListener('mouseover', onMouseOver, {once: true}); */
div.dataset.stickerSet = set.id;
div.dataset.access_hash = set.access_hash;
div.dataset.stickerSet = '' + set.id;
div.dataset.access_hash = '' + set.access_hash;
div.dataset.title = set.title;
div.append(header, stickersDiv);

View File

@ -18,8 +18,8 @@ import { ChatPermissions } from "./groupPermissions";
export default class AppUserPermissionsTab extends SliderSuperTabEventable {
public participant: ChannelParticipant;
public chatId: number;
public userId: number;
public chatId: ChatId;
public userId: UserId;
protected init() {
this.container.classList.add('edit-peer-container', 'user-permissions-container');
@ -40,7 +40,7 @@ export default class AppUserPermissionsTab extends SliderSuperTabEventable {
div.append(list);
const {dom} = appDialogsManager.addDialogNew({
dialog: this.userId,
dialog: this.userId.toPeerId(false),
container: list,
drawStatus: false,
rippleEnabled: true,

View File

@ -10,6 +10,7 @@ import appNavigationController, { NavigationItem } from "./appNavigationControll
import SliderSuperTab, { SliderSuperTabConstructable, SliderTab } from "./sliderTab";
import { safeAssign } from "../helpers/object";
import { attachClickEvent } from "../helpers/dom/clickEvent";
import { indexOfAndSplice } from "../helpers/array";
const TRANSITION_TIME = 250;
@ -110,7 +111,7 @@ export default class SidebarSlider {
}
public removeTabFromHistory(id: number | SliderSuperTab) {
this.historyTabIds.findAndSplice(i => i === id);
indexOfAndSplice(this.historyTabIds, id);
this.onCloseTab(id, undefined);
}

View File

@ -20,6 +20,17 @@ rootScope.addEventListener('context_menu_toggle', (visible) => {
RESET_GLOBAL = visible;
});
export type SwipeHandlerOptions = {
element: SwipeHandler['element'],
onSwipe: SwipeHandler['onSwipe'],
verifyTouchTarget?: SwipeHandler['verifyTouchTarget'],
onFirstSwipe?: SwipeHandler['onFirstSwipe'],
onReset?: SwipeHandler['onReset'],
cursor?: SwipeHandler['cursor'],
cancelEvent?: SwipeHandler['cancelEvent'],
listenerOptions?: SwipeHandler['listenerOptions']
};
export default class SwipeHandler {
private element: HTMLElement;
private onSwipe: (xDiff: number, yDiff: number, e: TouchEvent | MouseEvent) => boolean | void;
@ -28,20 +39,13 @@ export default class SwipeHandler {
private onReset: () => void;
private cursor: 'grabbing' | 'move' = 'grabbing';
private cancelEvent = true;
private listenerOptions: boolean | AddEventListenerOptions = false;
private hadMove = false;
private xDown: number = null;
private yDown: number = null;
constructor(options: {
element: SwipeHandler['element'],
onSwipe: SwipeHandler['onSwipe'],
verifyTouchTarget?: SwipeHandler['verifyTouchTarget'],
onFirstSwipe?: SwipeHandler['onFirstSwipe'],
onReset?: SwipeHandler['onReset'],
cursor?: SwipeHandler['cursor'],
cancelEvent?: SwipeHandler['cancelEvent']
}) {
constructor(options: SwipeHandlerOptions) {
safeAssign(this, options);
this.setListeners();
@ -49,20 +53,20 @@ export default class SwipeHandler {
public setListeners() {
if(!IS_TOUCH_SUPPORTED) {
this.element.addEventListener('mousedown', this.handleStart, false);
this.element.addEventListener('mousedown', this.handleStart, this.listenerOptions);
attachGlobalListenerTo.addEventListener('mouseup', this.reset);
} else {
this.element.addEventListener('touchstart', this.handleStart, false);
this.element.addEventListener('touchstart', this.handleStart, this.listenerOptions);
attachGlobalListenerTo.addEventListener('touchend', this.reset);
}
}
public removeListeners() {
if(!IS_TOUCH_SUPPORTED) {
this.element.removeEventListener('mousedown', this.handleStart, false);
this.element.removeEventListener('mousedown', this.handleStart, this.listenerOptions);
attachGlobalListenerTo.removeEventListener('mouseup', this.reset);
} else {
this.element.removeEventListener('touchstart', this.handleStart, false);
this.element.removeEventListener('touchstart', this.handleStart, this.listenerOptions);
attachGlobalListenerTo.removeEventListener('touchend', this.reset);
}
}

View File

@ -16,7 +16,7 @@ export class UsernameInputField extends InputField {
private checkUsernamePromise: Promise<any>;
private checkUsernameDebounced: (username: string) => void;
public options: InputFieldOptions & {
peerId: number,
peerId?: PeerId,
listenerSetter: ListenerSetter,
onChange?: () => void,
invalidText: LangPackKey,
@ -68,7 +68,7 @@ export class UsernameInputField extends InputField {
if(this.options.peerId) {
this.checkUsernamePromise = apiManager.invokeApi('channels.checkUsername', {
channel: appChatsManager.getChannelInput(-this.options.peerId),
channel: appChatsManager.getChannelInput(this.options.peerId.toChatId()),
username
});
} else {

View File

@ -570,7 +570,7 @@ export function wrapDocument({message, withTime, fontWeight, voiceAsMusic, showS
let docDiv = document.createElement('div');
docDiv.classList.add('document', `ext-${ext}`);
docDiv.dataset.docId = doc.id;
docDiv.dataset.docId = '' + doc.id;
const icoDiv = document.createElement('div');
icoDiv.classList.add('document-ico');
@ -580,7 +580,7 @@ export function wrapDocument({message, withTime, fontWeight, voiceAsMusic, showS
docDiv.classList.add('document-with-thumb');
let imgs: HTMLImageElement[] = [];
if(message.pFlags.is_outgoing) {
if(uploading) {
icoDiv.innerHTML = `<img src="${cacheContext.url}">`;
imgs.push(icoDiv.firstElementChild as HTMLImageElement);
} else {
@ -640,9 +640,9 @@ export function wrapDocument({message, withTime, fontWeight, voiceAsMusic, showS
docDiv.prepend(icoDiv);
if(!uploading && message.pFlags.is_outgoing) {
/* if(!uploading && message.pFlags.is_outgoing) {
return docDiv;
}
} */
let downloadDiv: HTMLElement, preloader: ProgressivePreloader = null;
const onLoad = () => {
@ -1133,7 +1133,7 @@ export function wrapSticker({doc, div, middleware, lazyLoadQueue, group, play, o
throw new Error('wrong doc for wrapSticker!');
}
div.dataset.docId = doc.id;
div.dataset.docId = '' + doc.id;
div.classList.add('media-sticker-wrapper');
//console.log('wrap sticker', doc, div, onlyThumb);
@ -1185,7 +1185,7 @@ export function wrapSticker({doc, div, middleware, lazyLoadQueue, group, play, o
renderImageFromUrl(thumbImage, appPhotosManager.getPreviewURLFromThumb(doc, thumb as PhotoSize.photoStrippedSize, true), afterRender);
haveThumbCached = true;
} else {
webpWorkerController.convert(doc.id, (thumb as PhotoSize.photoStrippedSize).bytes as Uint8Array).then(bytes => {
webpWorkerController.convert('' + doc.id, (thumb as PhotoSize.photoStrippedSize).bytes as Uint8Array).then(bytes => {
(thumb as PhotoSize.photoStrippedSize).bytes = bytes;
doc.pFlags.stickerThumbConverted = true;
@ -1594,6 +1594,7 @@ export function wrapAlbum({groupId, attachmentDiv, middleware, uploading, lazyLo
const div = attachmentDiv.children[idx] as HTMLElement;
div.dataset.mid = '' + message.mid;
div.dataset.peerId = '' + message.peerId;
const mediaDiv = div.firstElementChild as HTMLElement;
if(media._ === 'photo') {
wrapPhoto({

View File

@ -18,7 +18,8 @@ const App = {
hash: process.env.API_HASH,
version: process.env.VERSION,
versionFull: process.env.VERSION_FULL,
langPackVersion: '0.3.3',
build: +process.env.BUILD,
langPackVersion: '0.3.5',
langPack: 'macos',
langPackCode: 'en',
domains: [MAIN_DOMAIN] as string[],

19
src/global.d.ts vendored
View File

@ -1,4 +1,5 @@
import type ListenerSetter from "./helpers/listenerSetter";
import type { Chat, Document, User } from "./layer";
declare global {
interface AddEventListenerOptions extends EventListenerOptions {
@ -7,6 +8,24 @@ declare global {
// ls?: ListenerSetter;
}
type UserId = User.user['id'];
type ChatId = Chat.chat['id'];
// type PeerId = `u${UserId}` | `c${ChatId}`;
// type PeerId = `${UserId}` | `-${ChatId}`;
type PeerId = number;
// type PeerId = number;
type BotId = UserId;
type DocId = Document.document['id'];
type Long = string | number;
type MTLong = string;
type LocalErrorType = 'DOWNLOAD_CANCELED';
type ServerErrorType = 'FILE_REFERENCE_EXPIRED';
interface Error {
type?: LocalErrorType | ServerErrorType;
}
declare module 'worker-loader!*' {
class WebpackWorker extends Worker {
constructor();

View File

@ -21,6 +21,12 @@ export function listMergeSorted(list1: any[] = [], list2: any[] = []) {
export const accumulate = (arr: number[], initialValue: number) => arr.reduce((acc, value) => acc + value, initialValue);
export function indexOfAndSplice<T>(array: Array<T>, item: T) {
const idx = array.indexOf(item);
const spliced = idx !== -1 && array.splice(idx, 1);
return spliced && spliced[0];
}
export function findAndSpliceAll<T>(array: Array<T>, verify: (value: T, index: number, arr: typeof array) => boolean) {
const out: typeof array = [];
let idx = -1;

View File

@ -4,17 +4,18 @@
* https://github.com/morethanwords/tweb/blob/master/LICENSE
*/
import { Photo } from "../layer";
import appPhotosManager from "../lib/appManagers/appPhotosManager";
import ListLoader, { ListLoaderOptions } from "./listLoader";
export default class AvatarListLoader<Item extends {photoId: string}> extends ListLoader<Item, any> {
private peerId: number;
export default class AvatarListLoader<Item extends {photoId: Photo.photo['id']}> extends ListLoader<Item, any> {
private peerId: PeerId;
constructor(options: Omit<ListLoaderOptions<Item, any>, 'loadMore'> & {peerId: number}) {
constructor(options: Omit<ListLoaderOptions<Item, any>, 'loadMore'> & {peerId: PeerId}) {
super({
...options,
loadMore: (anchor, older, loadCount) => {
if(this.peerId < 0 || !older) return Promise.resolve({count: 0, items: []}); // ! это значит, что открыло аватар чата, но следующих фотографий нет.
if(this.peerId.isAnyChat() || !older) return Promise.resolve({count: 0, items: []}); // ! это значит, что открыло аватар чата, но следующих фотографий нет.
const maxId = anchor?.photoId;
return appPhotosManager.getUserPhotos(this.peerId, maxId, loadCount).then(value => {

View File

@ -0,0 +1,15 @@
export default function compareVersion(v1: string, v2: string): number {
v1 = v1.split(' ', 1)[0];
v2 = v2.split(' ', 1)[0];
const s1 = v1.split('.');
const s2 = v2.split('.');
for(let i = 0; i < s1.length; ++i) {
const v1 = +s1[i];
const v2 = +s2[i];
if(v1 > v2) return 1;
else if(v1 < v2) return -1;
}
return 0;
}

View File

@ -77,7 +77,7 @@ export default function getRichElementValue(node: HTMLElement, lines: string[],
_: tag.entityName,
offset: offset.offset,
length: nodeValue.length,
user_id: +parentElement.dataset.follow
user_id: parentElement.dataset.follow
});
} else {
entities.push({

View File

@ -0,0 +1,50 @@
/*
* https://github.com/morethanwords/tweb
* Copyright (C) 2019-2021 Eduard Kuzmenko
* https://github.com/morethanwords/tweb/blob/master/LICENSE
*/
import SwipeHandler, { SwipeHandlerOptions } from "../../components/swipeHandler";
import { IS_APPLE_MOBILE, IS_SAFARI } from "../../environment/userAgent";
import { cancelEvent } from "./cancelEvent";
import findUpClassName from "./findUpClassName";
import isSwipingBackSafari from "./isSwipingBackSafari";
export type SwipeHandlerHorizontalOptions = SwipeHandlerOptions & {
// xThreshold?: number
};
export default function handleHorizontalSwipe(options: SwipeHandlerHorizontalOptions) {
let cancelY = false;
return new SwipeHandler({
...options,
verifyTouchTarget: (e) => {
return !findUpClassName(e.target, 'progress-line') &&
!isSwipingBackSafari(e) &&
(options.verifyTouchTarget ? options.verifyTouchTarget(e) : true);
},
onSwipe: (xDiff, yDiff, e) => {
if(!cancelY && Math.abs(yDiff) > 20) {
return true;
}
if(Math.abs(xDiff) > Math.abs(yDiff)) {
cancelEvent(e);
cancelY = true;
} else if(!cancelY && Math.abs(yDiff) > Math.abs(xDiff)/* || Math.abs(yDiff) > 20 */) {
return true;
}
/* if(!cancelY && options.xThreshold !== undefined && xDiff >= options.xThreshold) {
cancelY = true;
} */
return options.onSwipe(xDiff, yDiff, e);
},
onReset: () => {
cancelY = false;
options.onReset && options.onReset();
},
cancelEvent: true
});
}

View File

@ -5,52 +5,18 @@
*/
import { cancelContextMenuOpening } from "../../components/misc";
import SwipeHandler from "../../components/swipeHandler";
import { cancelEvent } from "./cancelEvent";
import handleHorizontalSwipe, { SwipeHandlerHorizontalOptions } from "./handleHorizontalSwipe";
export default function handleTabSwipe(container: HTMLElement, onSwipe: (next: boolean) => void) {
/* let hadScroll = false;
const onScroll = () => {
swipeHandler.reset();
};
let firstSwipeChecked = false; */
return new SwipeHandler({
element: container,
/* onFirstSwipe: () => {
this.scroll.container.addEventListener('scroll', onScroll, {passive: true});
}, */
export default function handleTabSwipe(options: SwipeHandlerHorizontalOptions) {
return handleHorizontalSwipe({
...options,
onSwipe: (xDiff, yDiff, e) => {
/* if(!firstSwipeChecked) {
firstSwipeChecked = true;
if(yDiff !== 0) {
return true;
}
}
cancelEvent(e); */
if(Math.abs(yDiff) > 20) {
return true;
}
if(Math.abs(xDiff) > Math.abs(yDiff)) {
cancelEvent(e);
} else if(Math.abs(yDiff) > Math.abs(xDiff)/* || Math.abs(yDiff) > 20 */) {
return true;
}
if(Math.abs(xDiff) > 50) {
onSwipe(xDiff > 0);
options.onSwipe(xDiff, yDiff, e);
cancelContextMenuOpening();
return true;
}
},
/* onReset: () => {
hadScroll = false;
firstSwipeChecked = false;
this.scroll.container.removeEventListener('scroll', onScroll);
}, */
cancelEvent: false
}
});
}
}

View File

@ -0,0 +1,11 @@
/*
* https://github.com/morethanwords/tweb
* Copyright (C) 2019-2021 Eduard Kuzmenko
* https://github.com/morethanwords/tweb/blob/master/LICENSE
*/
import { IS_MOBILE_SAFARI } from "../../environment/userAgent";
export default function isSwipingBackSafari(e: TouchEvent | MouseEvent) {
return IS_MOBILE_SAFARI && e instanceof TouchEvent && e.touches[0].clientX < 30;
}

View File

@ -0,0 +1,25 @@
/*
* https://github.com/morethanwords/tweb
* Copyright (C) 2019-2021 Eduard Kuzmenko
* https://github.com/morethanwords/tweb/blob/master/LICENSE
*/
import { cancelEvent } from "./cancelEvent";
export default function lockTouchScroll(container: HTMLElement) {
const onTouchMove = (e: TouchEvent) => {
cancelEvent(e);
};
let lockers = 2;
const cb = () => {
if(!--lockers) {
container.removeEventListener('touchmove', onTouchMove, {capture: true});
}
};
container.addEventListener('touchmove', onTouchMove, {capture: true, passive: false});
container.addEventListener('touchend', cb, {once: true});
return cb;
}

View File

@ -46,10 +46,10 @@ export function deepEqual(x: any, y: any): boolean {
) : (x === y);
}
export function defineNotNumerableProperties(obj: {[key: string]: any}, names: string[]) {
export function defineNotNumerableProperties<T extends any>(obj: T, names: (keyof T)[]) {
//const perf = performance.now();
const props = {writable: true, configurable: true};
const out: {[name: string]: typeof props} = {};
const out: {[name in keyof T]?: typeof props} = {};
names.forEach(name => {
if(obj[name] === undefined) {
out[name] = props;
@ -108,7 +108,7 @@ export function safeReplaceArrayInObject<K>(key: K, wasObject: any, newObject: a
}
}
export function isObject(object: any) {
export function isObject<T extends Record<any, any>>(object: any): object is T {
return typeof(object) === 'object' && object !== null;
}

Some files were not shown because too many files have changed in this diff Show More