Premium gifts

Reply timecodes
Fix stuck chat history
This commit is contained in:
Eduard Kuzmenko 2023-03-08 01:25:07 +04:00
parent 5b1097d7f9
commit c1f87d1b3e
65 changed files with 872 additions and 384 deletions

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 240 KiB

After

Width:  |  Height:  |  Size: 241 KiB

Binary file not shown.

Binary file not shown.

View File

@ -29,6 +29,7 @@ import PopupForward from './popups/forward';
import Scrollable from './scrollable';
import appSidebarRight from './sidebarRight';
import AppSharedMediaTab from './sidebarRight/tabs/sharedMedia';
import PopupElement from './popups';
type AppMediaViewerTargetType = {
element: HTMLElement,
@ -193,7 +194,7 @@ export default class AppMediaViewer extends AppMediaViewerBase<'caption', 'delet
onDeleteClick = () => {
const target = this.target;
new PopupDeleteMessages(target.peerId, [target.mid], 'chat', () => {
PopupElement.createPopup(PopupDeleteMessages, target.peerId, [target.mid], 'chat', () => {
this.target = {element: this.content.media} as any;
this.close();
});
@ -203,7 +204,7 @@ export default class AppMediaViewer extends AppMediaViewerBase<'caption', 'delet
const target = this.target;
if(target.mid) {
// appSidebarRight.forwardTab.open([target.mid]);
new PopupForward({
PopupElement.createPopup(PopupForward, {
[target.peerId]: [target.mid]
}, () => {
return this.close();

View File

@ -1151,7 +1151,7 @@
// onForwardClick = () => {
// if(this.currentMessageId) {
// //appSidebarRight.forwardTab.open([this.currentMessageId]);
// new PopupForward(this.currentPeerId, [this.currentMessageId], () => {
// PopupElement.createPopup(PopupForward(this.currentPeerId, [this.currentMessageId], , ) => {
// return this.close();
// });
// }

View File

@ -77,6 +77,7 @@ import noop from '../helpers/noop';
import wrapMediaSpoiler, {onMediaSpoilerClick} from './wrappers/mediaSpoiler';
import filterAsync from '../helpers/array/filterAsync';
import ChatContextMenu from './chat/contextMenu';
import PopupElement from './popups';
// const testScroll = false;
@ -257,7 +258,7 @@ class SearchContextMenu {
if(this.searchSuper.selection.isSelecting) {
simulateClickEvent(this.searchSuper.selection.selectionForwardBtn);
} else {
new PopupForward({
PopupElement.createPopup(PopupForward, {
[this.peerId]: [this.mid]
});
}
@ -275,7 +276,7 @@ class SearchContextMenu {
if(this.searchSuper.selection.isSelecting) {
simulateClickEvent(this.searchSuper.selection.selectionDeleteBtn);
} else {
new PopupDeleteMessages(this.peerId, [this.mid], 'chat');
PopupElement.createPopup(PopupDeleteMessages, this.peerId, [this.mid], 'chat');
}
};
}

View File

@ -4,7 +4,7 @@
* https://github.com/morethanwords/tweb/blob/master/LICENSE
*/
import {i18n, LangPackKey} from '../lib/langPack';
import {FormatterArguments, i18n, LangPackKey} from '../lib/langPack';
import ripple from './ripple';
export type ButtonOptions = Partial<{
@ -13,6 +13,7 @@ export type ButtonOptions = Partial<{
icon: string,
rippleSquare: true,
text: LangPackKey,
textArgs?: FormatterArguments,
disabled: boolean,
asDiv: boolean,
asLink: boolean
@ -39,7 +40,7 @@ export default function Button<T extends ButtonOptions>(className: string, optio
}
if(options.text) {
button.append(i18n(options.text));
button.append(i18n(options.text, options.textArgs));
}
return button as any;

View File

@ -8,6 +8,7 @@ import Button from './button';
const ButtonCorner = (options: Partial<{className: string, icon: string, noRipple: true, onlyMobile: true, asDiv: boolean}> = {}) => {
const button = Button('btn-circle btn-corner z-depth-1' + (options.className ? ' ' + options.className : ''), options);
button.tabIndex = -1;
return button;
};

View File

@ -785,13 +785,30 @@ export default class ChatBubbles {
const bubble = this.bubbles[mid];
if(!bubble) return;
const message = (await this.chat.getMessage(mid)) as Message.message;
const [message, originalMessage] = await Promise.all([
(await this.chat.getMessage(mid)) as Message.message,
(await this.managers.appMessagesManager.getMessageByPeer(replyToPeerId, replyMid)) as Message.message
]);
if(!middleware()) return;
MessageRender.setReply({
chat: this.chat,
bubble,
message
});
let maxMediaTimestamp: number;
const timestamps = bubble.querySelectorAll<HTMLAnchorElement>('.timestamp');
if(originalMessage && (maxMediaTimestamp = getMediaDurationFromMessage(originalMessage))) {
timestamps.forEach((timestamp) => {
const value = +timestamp.dataset.timestamp;
if(value < maxMediaTimestamp) {
timestamp.classList.remove('is-disabled');
} else {
timestamp.removeAttribute('href');
}
});
}
});
});
});
@ -1787,7 +1804,8 @@ export default class ChatBubbles {
return;
}
new PopupPayment(
PopupElement.createPopup(
PopupPayment,
message as Message.message,
await this.managers.appPaymentsManager.getInputInvoiceByPeerId(message.peerId, message.mid)
);
@ -1878,7 +1896,7 @@ export default class ChatBubbles {
const message = await this.managers.appMessagesManager.getMessageByPeer(peerId.toPeerId(), +mid);
if(message) {
const inputInvoice = await this.managers.appPaymentsManager.getInputInvoiceByPeerId(this.peerId, +bubble.dataset.mid);
new PopupPayment(message as Message.message, inputInvoice, undefined, true);
PopupElement.createPopup(PopupPayment, message as Message.message, inputInvoice, undefined, true);
}
} else {
this.chat.appImManager.setInnerPeer({
@ -1916,7 +1934,7 @@ export default class ChatBubbles {
const doc = ((message as Message.message).media as MessageMedia.messageMediaDocument)?.document as Document.document;
if(doc?.stickerSetInput) {
new PopupStickers(doc.stickerSetInput).show();
PopupElement.createPopup(PopupStickers, doc.stickerSetInput).show();
}
return;
@ -1941,7 +1959,7 @@ export default class ChatBubbles {
} else if(target.classList.contains('forward')) {
const mid = +bubble.dataset.mid;
const message = await this.managers.appMessagesManager.getMessageByPeer(this.peerId, mid);
new PopupForward({
PopupElement.createPopup(PopupForward, {
[this.peerId]: await this.managers.appMessagesManager.getMidsByMessage(message)
});
// appSidebarRight.forwardTab.open([mid]);
@ -3024,6 +3042,16 @@ export default class ChatBubbles {
this.chat.input.setStartParam(startParam);
}
if(options.mediaTimestamp) {
getHeavyAnimationPromise().then(() => {
this.playMediaWithTimestampAndMid({
lastMsgId,
middleware,
mediaTimestamp: options.mediaTimestamp
});
});
}
return null;
}
} else {
@ -3251,13 +3279,11 @@ export default class ChatBubbles {
pause(400) :
Promise.resolve();
p.then(() => {
return this.getMountedBubble(lastMsgId);
}).then((mounted) => {
if(!middleware() || !mounted) {
return;
}
this.playMediaWithTimestamp(mounted.bubble, options.mediaTimestamp);
return this.playMediaWithTimestampAndMid({
lastMsgId,
middleware,
mediaTimestamp: options.mediaTimestamp
});
});
}
@ -3305,6 +3331,24 @@ export default class ChatBubbles {
return {cached, promise: setPeerPromise};
}
public playMediaWithTimestampAndMid({
middleware,
lastMsgId,
mediaTimestamp
}: {
middleware: () => boolean,
lastMsgId: number,
mediaTimestamp: number
}) {
this.getMountedBubble(lastMsgId).then((mounted) => {
if(!middleware() || !mounted) {
return;
}
this.playMediaWithTimestamp(mounted.bubble, mediaTimestamp);
});
}
public playMediaWithTimestamp(element: HTMLElement, timestamp: number) {
const bubble = findUpClassName(element, 'bubble');
const groupedItem = findUpClassName(element, 'grouped-item');
@ -3325,6 +3369,19 @@ export default class ChatBubbles {
audio.playWithTimestamp(timestamp);
return;
}
const replyToPeerId = bubble.dataset.replyToPeerId.toPeerId();
const replyToMid = +bubble.dataset.replyToMid;
if(replyToPeerId && replyToMid) {
if(replyToPeerId === this.peerId) {
this.chat.setMessageId(replyToMid, timestamp);
} else {
this.chat.appImManager.setInnerPeer({
peerId: replyToPeerId,
mediaTimestamp: timestamp
});
}
}
}
private async setFetchReactionsInterval(afterSetPromise: Promise<any>) {
@ -3937,11 +3994,28 @@ export default class ChatBubbles {
customEmojiSize ??= this.chat.appImManager.customEmojiSize;
const maxMediaTimestamp = getMediaDurationFromMessage(albumTextMessage || message as Message.message);
let maxMediaTimestamp = getMediaDurationFromMessage(albumTextMessage || message as Message.message);
if(albumTextMessage && needToSetHTML) {
bubble.dataset.textMid = '' + albumTextMessage.mid;
}
if(message.reply_to) {
const replyToPeerId = message.reply_to.reply_to_peer_id ? getPeerId(message.reply_to.reply_to_peer_id) : this.peerId;
bubble.dataset.replyToPeerId = '' + replyToPeerId;
bubble.dataset.replyToMid = '' + message.reply_to_mid;
if(maxMediaTimestamp === undefined) {
const originalMessage = await rootScope.managers.appMessagesManager.getMessageByPeer(replyToPeerId, message.reply_to_mid);
if(originalMessage) {
maxMediaTimestamp = getMediaDurationFromMessage(originalMessage as Message.message);
} else {
// this.managers.appMessagesManager.fetchMessageReplyTo(message);
// this.needUpdate.push({replyToPeerId, replyMid: message.reply_to_mid, mid: message.mid});
maxMediaTimestamp = Infinity;
}
}
}
const richTextOptions: Parameters<typeof wrapRichText>[1] = {
entities: totalEntities,
passEntities: this.passEntities,
@ -4095,7 +4169,7 @@ export default class ChatBubbles {
}
return new Promise<PeerId>((resolve, reject) => {
const popup = new PopupForward(undefined, (peerId) => {
const popup = PopupElement.createPopup(PopupForward, undefined, (peerId) => {
resolve(peerId);
});
@ -5726,7 +5800,7 @@ export default class ChatBubbles {
if(sponsoredMessage.chat_invite) {
callback = () => {
new PopupJoinChatInvite(sponsoredMessage.chat_invite_hash, sponsoredMessage.chat_invite as ChatInvite.chatInvite);
PopupElement.createPopup(PopupJoinChatInvite, sponsoredMessage.chat_invite_hash, sponsoredMessage.chat_invite as ChatInvite.chatInvite);
};
} else if(sponsoredMessage.chat_invite_hash) {
callback = () => {
@ -6147,6 +6221,11 @@ export default class ChatBubbles {
return result;
}
// private async getDiscussionMessages() {
// const mids = await this.chat.getMidsByMid(this.chat.threadId);
// return Promise.all(mids.map((mid) => this.chat.getMessage(mid)));
// }
/**
* Load and render history
* @param maxId max message id

View File

@ -571,11 +571,12 @@ export default class Chat extends EventListenerBase<{
this.autoDownload = await getAutoDownloadSettingsByPeerId(this.peerId);
}
public setMessageId(messageId?: number) {
public setMessageId(messageId?: number, mediaTimestamp?: number) {
return this.setPeer({
peerId: this.peerId,
threadId: this.threadId,
lastMsgId: messageId
lastMsgId: messageId,
mediaTimestamp
});
}
@ -711,4 +712,9 @@ export default class Chat extends EventListenerBase<{
public isPinnedMessagesNeeded() {
return this.type === 'chat' || this.isForum;
}
public canGiftPremium() {
const peerId = this.peerId;
return peerId.isUser() && this.managers.appProfileManager.canGiftPremium(this.peerId.toUserId());
}
}

View File

@ -48,6 +48,7 @@ import PopupStickers from '../popups/stickers';
import getMediaFromMessage from '../../lib/appManagers/utils/messages/getMediaFromMessage';
import canSaveMessageMedia from '../../lib/appManagers/utils/messages/canSaveMessageMedia';
import getAlbumText from '../../lib/appManagers/utils/messages/getAlbumText';
import PopupElement from '../popups';
export default class ChatContextMenu {
private buttons: (ButtonMenuItemOptions & {verify: () => boolean | Promise<boolean>, notDirect?: () => boolean, withSelection?: true, isSponsored?: true, localName?: 'views' | 'emojis'})[];
@ -490,7 +491,7 @@ export default class ChatContextMenu {
icon: 'flag',
text: 'ReportChat',
onClick: () => {
new PopupReportMessages(this.peerId, [this.mid]);
PopupElement.createPopup(PopupReportMessages, this.peerId, [this.mid]);
},
verify: async() => !this.message.pFlags.out && this.message._ === 'message' && !this.message.pFlags.is_outgoing && await this.managers.appPeersManager.isChannel(this.peerId),
notDirect: () => true,
@ -516,7 +517,7 @@ export default class ChatContextMenu {
peerId: this.viewerPeerId
});
} else if(this.canOpenReactedList) {
new PopupReactedList(this.message as Message.message);
PopupElement.createPopup(PopupReactedList, this.message as Message.message);
} else {
return false;
}
@ -540,7 +541,7 @@ export default class ChatContextMenu {
icon: 'info',
text: 'Chat.Message.Sponsored.What',
onClick: () => {
new PopupSponsored();
PopupElement.createPopup(PopupSponsored);
},
verify: () => false,
isSponsored: true
@ -549,7 +550,7 @@ export default class ChatContextMenu {
text: 'Loading',
onClick: () => {
this.emojiInputsPromise.then((inputs) => {
new PopupStickers(inputs, true).show();
PopupElement.createPopup(PopupStickers, inputs, true).show();
});
},
verify: () => !!this.getUniqueCustomEmojisFromMessage().length,
@ -890,7 +891,7 @@ export default class ChatContextMenu {
if(this.chat.selection.isSelecting) {
simulateClickEvent(this.chat.selection.selectionSendNowBtn);
} else {
new PopupSendNow(this.peerId, await this.chat.getMidsByMid(this.mid));
PopupElement.createPopup(PopupSendNow, this.peerId, await this.chat.getMidsByMid(this.mid));
}
};
@ -929,11 +930,11 @@ export default class ChatContextMenu {
};
private onPinClick = () => {
new PopupPinMessage(this.peerId, this.mid);
PopupElement.createPopup(PopupPinMessage, this.peerId, this.mid);
};
private onUnpinClick = () => {
new PopupPinMessage(this.peerId, this.mid, true);
PopupElement.createPopup(PopupPinMessage, this.peerId, this.mid, true);
};
private onRetractVote = () => {
@ -968,7 +969,7 @@ export default class ChatContextMenu {
if(this.chat.selection.isSelecting) {
simulateClickEvent(this.chat.selection.selectionDeleteBtn);
} else {
new PopupDeleteMessages(this.peerId, this.isTargetAGroupedItem ? [this.mid] : await this.chat.getMidsByMid(this.mid), this.chat.type);
PopupElement.createPopup(PopupDeleteMessages, this.peerId, this.isTargetAGroupedItem ? [this.mid] : await this.chat.getMidsByMid(this.mid), this.chat.type);
}
};

View File

@ -111,6 +111,7 @@ import {MARKDOWN_ENTITIES} from '../../lib/richTextProcessor';
import IMAGE_MIME_TYPES_SUPPORTED from '../../environment/imageMimeTypesSupport';
import VIDEO_MIME_TYPES_SUPPORTED from '../../environment/videoMimeTypesSupport';
import {ChatRights} from '../../lib/appManagers/appChatsManager';
import PopupGiftPremium from '../popups/giftPremium';
const RECORD_MIN_TIME = 500;
@ -644,6 +645,11 @@ export default class ChatInput {
this.fileInput.click();
}
// verify: () => this.chat.canSend('send_docs')
}, {
icon: 'gift',
text: 'GiftPremium',
onClick: () => this.chat.appImManager.giftPremium(this.chat.peerId),
verify: () => this.chat.canGiftPremium()
}, {
icon: 'poll',
text: 'Poll',
@ -1049,7 +1055,7 @@ export default class ChatInput {
this.listenerSetter.add(this.pinnedControlBtn)('click', () => {
const peerId = this.chat.peerId;
new PopupPinMessage(peerId, 0, true, () => {
PopupElement.createPopup(PopupPinMessage, peerId, 0, true, () => {
this.chat.appImManager.setPeer(); // * close tab
// ! костыль, это скроет закреплённые сообщения сразу, вместо того, чтобы ждать пока анимация перехода закончится
@ -1219,7 +1225,7 @@ export default class ChatInput {
const middleware = this.chat.bubbles.getMiddleware();
const canSendWhenOnline = rootScope.myId !== peerId && peerId.isUser() && await this.managers.appUsersManager.isUserOnlineVisible(peerId);
new PopupSchedule(initDate, (timestamp) => {
PopupElement.createPopup(PopupSchedule, initDate, (timestamp) => {
if(!middleware()) {
return;
}
@ -2470,7 +2476,7 @@ export default class ChatInput {
opusDecodeController.setKeepAlive(true);
const showDiscardPopup = () => {
new PopupPeer('popup-cancel-record', {
PopupElement.createPopup(PopupPeer, 'popup-cancel-record', {
titleLangKey: 'DiscardVoiceMessageTitle',
descriptionLangKey: 'DiscardVoiceMessageDescription',
buttons: [{
@ -2614,7 +2620,7 @@ export default class ChatInput {
}
if(!draftsAreEqual(draft, originalDraft)) {
new PopupPeer('discard-editing', {
PopupElement.createPopup(PopupPeer, 'discard-editing', {
buttons: [{
langKey: 'Alert.Confirm.Discard',
callback: () => {
@ -2657,7 +2663,7 @@ export default class ChatInput {
this.clearHelper();
this.updateSendBtn();
let selected = false;
const popup = new PopupForward(forwarding, () => {
const popup = PopupElement.createPopup(PopupForward, forwarding, () => {
selected = true;
});
@ -2800,7 +2806,7 @@ export default class ChatInput {
this.onMessageSent();
} else {
new PopupDeleteMessages(peerId, [editMsgId], chat.type);
PopupElement.createPopup(PopupDeleteMessages, peerId, [editMsgId], chat.type);
return;
}

View File

@ -23,6 +23,7 @@ import throttle from '../../helpers/schedulers/throttle';
import {AppManagers} from '../../lib/appManagers/managers';
import {Message} from '../../layer';
import {logger} from '../../lib/logger';
import PopupElement from '../popups';
class AnimatedSuper {
static DURATION = 200;
@ -275,9 +276,9 @@ export default class ChatPinnedMessage {
divAndCaption: dAC,
onClose: async() => {
if(await managers.appPeersManager.canPinMessage(this.chat.peerId)) {
new PopupPinMessage(this.chat.peerId, this.pinnedMid, true);
PopupElement.createPopup(PopupPinMessage, this.chat.peerId, this.pinnedMid, true);
} else {
new PopupPinMessage(this.chat.peerId, 0, true);
PopupElement.createPopup(PopupPinMessage, this.chat.peerId, 0, true);
}
return false;

View File

@ -38,6 +38,7 @@ import {attachContextMenuListener} from '../../helpers/dom/attachContextMenuList
import filterUnique from '../../helpers/array/filterUnique';
import appImManager from '../../lib/appManagers/appImManager';
import {Message} from '../../layer';
import PopupElement from '../popups';
const accumulateMapSet = (map: Map<any, Set<number>>) => {
return [...map.values()].reduce((acc, v) => acc + v.size, 0);
@ -676,7 +677,7 @@ export class SearchSelection extends AppSelection {
obj[fromPeerId] = Array.from(mids).sort((a, b) => a - b);
}
new PopupForward(obj, () => {
PopupElement.createPopup(PopupForward, obj, () => {
this.cancelSelection();
});
}, attachClickOptions);
@ -685,7 +686,7 @@ export class SearchSelection extends AppSelection {
this.selectionDeleteBtn = ButtonIcon(`delete danger ${BASE_CLASS}-delete`);
attachClickEvent(this.selectionDeleteBtn, () => {
const peerId = [...this.selectedMids.keys()][0];
new PopupDeleteMessages(peerId, [...this.selectedMids.get(peerId)], 'chat', () => {
PopupElement.createPopup(PopupDeleteMessages, peerId, [...this.selectedMids.get(peerId)], 'chat', () => {
this.cancelSelection();
});
}, attachClickOptions);
@ -949,7 +950,7 @@ export default class ChatSelection extends AppSelection {
this.selectionSendNowBtn = Button('btn-primary btn-transparent btn-short text-bold selection-container-send', {icon: 'send2'});
this.selectionSendNowBtn.append(i18n('MessageScheduleSend'));
attachClickEvent(this.selectionSendNowBtn, () => {
new PopupSendNow(this.chat.peerId, [...this.selectedMids.get(this.chat.peerId)], () => {
PopupElement.createPopup(PopupSendNow, this.chat.peerId, [...this.selectedMids.get(this.chat.peerId)], () => {
this.cancelSelection();
});
}, attachClickOptions);
@ -962,7 +963,7 @@ export default class ChatSelection extends AppSelection {
obj[fromPeerId] = Array.from(mids).sort((a, b) => a - b);
}
new PopupForward(obj, () => {
PopupElement.createPopup(PopupForward, obj, () => {
this.cancelSelection();
});
}, attachClickOptions);
@ -971,7 +972,7 @@ export default class ChatSelection extends AppSelection {
this.selectionDeleteBtn = Button('btn-primary btn-transparent danger text-bold selection-container-delete', {icon: 'delete'});
this.selectionDeleteBtn.append(i18n('Delete'));
attachClickEvent(this.selectionDeleteBtn, () => {
new PopupDeleteMessages(this.chat.peerId, [...this.selectedMids.get(this.chat.peerId)], this.chat.type, () => {
PopupElement.createPopup(PopupDeleteMessages, this.chat.peerId, [...this.selectedMids.get(this.chat.peerId)], this.chat.type, () => {
this.cancelSelection();
});
}, attachClickOptions);

View File

@ -47,6 +47,7 @@ import apiManagerProxy from '../../lib/mtproto/mtprotoworker';
import {makeMediaSize} from '../../helpers/mediaSize';
import {FOLDER_ID_ALL} from '../../lib/mtproto/mtproto_config';
import formatNumber from '../../helpers/number/formatNumber';
import PopupElement from '../popups';
type ButtonToVerify = {element?: HTMLElement, verify: () => boolean | Promise<boolean>};
@ -419,11 +420,11 @@ export default class ChatTopbar {
text: 'ShareContact',
onClick: () => {
const contactPeerId = this.peerId;
new PopupPickUser({
PopupElement.createPopup(PopupPickUser, {
peerTypes: ['dialogs', 'contacts'],
onSelect: (peerId) => {
return new Promise((resolve, reject) => {
new PopupPeer('', {
PopupElement.createPopup(PopupPeer, '', {
titleLangKey: 'SendMessageTitle',
descriptionLangKey: 'SendContactToGroupText',
descriptionLangArgs: [new PeerTitle({peerId, dialog: true}).element],
@ -453,6 +454,11 @@ export default class ChatTopbar {
});
},
verify: async() => rootScope.myId !== this.peerId && this.peerId.isUser() && (await this.managers.appPeersManager.isContact(this.peerId)) && !!(await this.managers.appUsersManager.getUser(this.peerId.toUserId())).phone
}, {
icon: 'gift',
text: 'GiftPremium',
onClick: () => this.chat.appImManager.giftPremium(this.peerId),
verify: () => this.chat.canGiftPremium()
}, {
icon: 'bots',
text: 'Settings',
@ -471,7 +477,7 @@ export default class ChatTopbar {
icon: 'lock',
text: 'BlockUser',
onClick: () => {
new PopupPeer('', {
PopupElement.createPopup(PopupPeer, '', {
peerId: this.peerId,
titleLangKey: 'BlockUser',
descriptionLangKey: 'AreYouSureBlockContact2',
@ -512,7 +518,7 @@ export default class ChatTopbar {
icon: 'delete danger',
text: 'Delete',
onClick: () => {
new PopupDeleteDialog(this.peerId/* , 'leave' */);
PopupElement.createPopup(PopupDeleteDialog, this.peerId/* , 'leave' */);
},
verify: async() => this.chat.type === 'chat' && !!(await this.managers.appMessagesManager.getDialogOnly(this.peerId))
}];
@ -680,7 +686,7 @@ export default class ChatTopbar {
}
private onMuteClick = () => {
new PopupMute(this.peerId);
PopupElement.createPopup(PopupMute, this.peerId);
};
private onResize = () => {

View File

@ -26,7 +26,8 @@ export type CheckboxFieldOptions = {
restriction?: boolean,
withRipple?: boolean,
withHover?: boolean,
listenerSetter?: ListenerSetter
listenerSetter?: ListenerSetter,
asRadio?: boolean
};
export default class CheckboxField {
public input: HTMLInputElement;
@ -54,9 +55,9 @@ export default class CheckboxField {
const input = this.input = document.createElement('input');
input.classList.add('checkbox-field-input');
input.type = 'checkbox';
input.type = options.asRadio ? 'radio' : 'checkbox';
if(options.name) {
input.id = 'input-' + options.name;
input[options.asRadio ? 'name' : 'id'] = 'input-' + options.name;
}
if(options.checked) {

View File

@ -4,7 +4,7 @@
* https://github.com/morethanwords/tweb/blob/master/LICENSE
*/
import {addCancelButton} from './popups';
import PopupElement, {addCancelButton} from './popups';
import PopupPeer, {PopupPeerCheckboxOptions, PopupPeerOptions} from './popups/peer';
// type PopupConfirmationOptions = Pick<PopupPeerOptions, 'titleLangKey'>;
@ -35,6 +35,6 @@ export default function confirmationPopup<T extends PopupConfirmationOptions>(
options.buttons = buttons;
options.checkboxes ??= checkbox && [checkbox];
new PopupPeer('popup-confirmation', options).show();
PopupElement.createPopup(PopupPeer, 'popup-confirmation', options).show();
});
}

View File

@ -18,6 +18,7 @@ import {AppManagers} from '../lib/appManagers/managers';
import {GENERAL_TOPIC_ID} from '../lib/mtproto/mtproto_config';
import showLimitPopup from './popups/limit';
import createContextMenu from '../helpers/dom/createContextMenu';
import PopupElement from './popups';
export default class DialogsContextMenu {
private buttons: ButtonMenuItemOptionsVerifiable[];
@ -216,7 +217,7 @@ export default class DialogsContextMenu {
};
private onMuteClick = () => {
new PopupMute(this.peerId, this.threadId);
PopupElement.createPopup(PopupMute, this.peerId, this.threadId);
};
private onUnreadClick = async() => {
@ -233,6 +234,6 @@ export default class DialogsContextMenu {
};
private onDeleteClick = () => {
new PopupDeleteDialog(this.peerId, undefined, undefined, this.threadId);
PopupElement.createPopup(PopupDeleteDialog, this.peerId, undefined, undefined, this.threadId);
};
}

View File

@ -40,6 +40,7 @@ import {hideToast, toastNew} from '../../toast';
import safeAssign from '../../../helpers/object/safeAssign';
import type {AppStickersManager} from '../../../lib/appManagers/appStickersManager';
import liteMode from '../../../helpers/liteMode';
import PopupElement from '../../popups';
const loadedURLs: Set<string> = new Set();
export function appendEmoji(emoji: string, container?: HTMLElement, prepend = false, unify = false) {
@ -703,7 +704,7 @@ export default class EmojiTab extends EmoticonsTabC<EmojiTabCategory> {
return;
}
new PopupStickers({id: category.set.id, access_hash: category.set.access_hash}, true).show();
PopupElement.createPopup(PopupStickers, {id: category.set.id, access_hash: category.set.access_hash}, true).show();
return;
}

View File

@ -38,6 +38,7 @@ import {AnyFunction} from '../../../types';
import {IgnoreMouseOutType} from '../../../helpers/dropdownHover';
import customProperties from '../../../helpers/dom/customProperties';
import windowSize from '../../../helpers/windowSize';
import PopupElement from '../../popups';
export class SuperStickerRenderer {
public lazyLoadQueue: LazyLoadQueueRepeat;
@ -617,7 +618,7 @@ export default class StickersTab extends EmoticonsTabC<StickersTabCategory<Stick
return;
}
new PopupStickers({id: category.set.id, access_hash: category.set.access_hash}).show();
PopupElement.createPopup(PopupStickers, {id: category.set.id, access_hash: category.set.access_hash}).show();
return;
}

View File

@ -373,7 +373,7 @@ export default class PopupGroupCall extends PopupElement {
};
if(await this.managers.appChatsManager.hasRights(this.instance.chatId, 'manage_call')) {
new PopupPeer('popup-end-video-chat', {
PopupElement.createPopup(PopupPeer, 'popup-end-video-chat', {
titleLangKey: 'VoiceChat.End.Title',
descriptionLangKey: 'VoiceChat.End.Text',
checkboxes: [{

View File

@ -183,7 +183,7 @@ export default class PopupDeleteDialog {
}
}
new PopupPeer('popup-delete-chat', {
PopupElement.createPopup(PopupPeer, 'popup-delete-chat', {
peerId,
threadId,
titleLangKey: title,

View File

@ -102,7 +102,7 @@ export default class PopupDeleteMessages {
addCancelButton(buttons);
const popup = new PopupPeer('popup-delete-chat', {
const popup = PopupElement.createPopup(PopupPeer, 'popup-delete-chat', {
peerId,
threadId,
titleLangKey: title,

View File

@ -11,6 +11,7 @@ import rootScope from '../../lib/rootScope';
import {toastNew} from '../toast';
import PopupPickUser from './pickUser';
import getMediaFromMessage from '../../lib/appManagers/utils/messages/getMediaFromMessage';
import PopupElement from '.';
export default class PopupForward extends PopupPickUser {
constructor(
@ -115,6 +116,6 @@ export default class PopupForward extends PopupPickUser {
}
});
new PopupForward(args[0], args[1], Array.from(actions));
PopupElement.createPopup(PopupForward, args[0], args[1], Array.from(actions));
}
}

View File

@ -0,0 +1,121 @@
/*
* https://github.com/morethanwords/tweb
* Copyright (C) 2019-2021 Eduard Kuzmenko
* https://github.com/morethanwords/tweb/blob/master/LICENSE
*/
import PopupElement from '.';
import {attachClickEvent} from '../../helpers/dom/clickEvent';
import paymentsWrapCurrencyAmount from '../../helpers/paymentsWrapCurrencyAmount';
import {PremiumGiftOption} from '../../layer';
import appImManager from '../../lib/appManagers/appImManager';
import I18n, {i18n, _i18n} from '../../lib/langPack';
import AvatarElement from '../avatar';
import Button from '../button';
import CheckboxField from '../checkboxField';
import Row from '../row';
import wrapPeerTitle from '../wrappers/peerTitle';
const className = 'popup-gift-premium';
export default class PopupGiftPremium extends PopupElement {
constructor(
private peerId: PeerId,
private giftOptions: PremiumGiftOption[]
) {
super(className, {closable: true, overlayClosable: true, body: true, scrollable: true});
this.construct();
}
public async construct() {
const {peerId, giftOptions} = this;
const avatar = new AvatarElement();
avatar.classList.add('avatar-100', className + '-avatar');
await avatar.updateWithOptions({
peerId
});
const title = document.createElement('span');
_i18n(title, 'GiftTelegramPremiumTitle');
title.classList.add(className + '-title');
const subtitle = i18n('GiftTelegramPremiumDescription', [await wrapPeerTitle({peerId})]);
subtitle.classList.add(className + '-subtitle');
const shortestOption = this.giftOptions.slice().sort((a, b) => a.months - b.months)[0];
const wrapCurrency = (amount: number | string) => paymentsWrapCurrencyAmount(amount, shortestOption.currency, false, true);
const rows = this.giftOptions.map((giftOption, idx) => {
let subtitle = i18n('PricePerMonth', [wrapCurrency(+giftOption.amount / giftOption.months)]);
if(giftOption !== shortestOption) {
const span = document.createElement('span');
const badge = document.createElement('span');
badge.classList.add(className + '-discount');
const shortestAmount = +shortestOption.amount * giftOption.months / shortestOption.months;
const discount = Math.round((1 - +giftOption.amount / shortestAmount) * 100);
badge.textContent = '-' + discount + '%';
span.append(badge, subtitle);
subtitle = span;
}
const isYears = !(giftOption.months % 12);
const checkboxField = new CheckboxField({
// text: 'Months',
// textArgs: [giftOption.months],
checked: idx === 0,
round: true,
name: 'gift-months',
asRadio: true
});
const row = new Row({
title: i18n(isYears ? 'Years' : 'Months', [isYears ? giftOption.months / 12 : giftOption.months]),
checkboxField,
clickable: true,
subtitle,
titleRightSecondary: wrapCurrency(giftOption.amount)
});
row.container.classList.add(className + '-option');
return row;
});
const form = document.createElement('form');
form.classList.add(className + '-options');
form.append(...rows.map((row) => row.container));
const buttonText = new I18n.IntlElement({key: 'GiftSubscriptionFor', args: [wrapCurrency(giftOptions[0].amount)]});
const getSelectedOption = () => giftOptions[rows.findIndex((row) => row.checkboxField.checked)];
this.listenerSetter.add(form)('change', () => {
buttonText.compareAndUpdate({
args: [
wrapCurrency(getSelectedOption().amount)
]
});
});
const giftButton = Button(`btn-primary ${className}-confirm shimmer`);
giftButton.append(buttonText.element);
attachClickEvent(giftButton, () => {
const giftOption = getSelectedOption();
appImManager.openUrl(giftOption.bot_url);
this.hide();
}, {listenerSetter: this.listenerSetter});
this.scrollable.append(
avatar,
title,
subtitle,
form,
giftButton
);
this.show();
}
}

View File

@ -318,7 +318,7 @@ export default class PopupElement<T extends EventListenerListeners = {}> extends
return this.POPUPS.filter((element) => element instanceof popupConstructor) as T[];
}
public static createPopup<T extends PopupElement, A extends Array<any>>(ctor: {new(...args: A): T}, ...args: A) {
public static createPopup<T extends /* PopupElement */any, A extends Array<any>>(ctor: {new(...args: A): T}, ...args: A) {
const popup = new ctor(...args);
return popup;
}

View File

@ -531,7 +531,7 @@ export default class PopupPayment extends PopupElement {
};
const onMethodClick = () => {
new PopupPaymentCard(paymentForm as PaymentsPaymentForm, previousCardDetails as PaymentCardDetails).addEventListener('finish', ({token, card}) => {
PopupElement.createPopup(PopupPaymentCard, paymentForm as PaymentsPaymentForm, previousCardDetails as PaymentCardDetails).addEventListener('finish', ({token, card}) => {
previousToken = token, previousCardDetails = card;
setCardSubtitle(card);
@ -587,7 +587,7 @@ export default class PopupPayment extends PopupElement {
if(!isReceipt) {
onShippingAddressClick = (focus) => {
new PopupPaymentShipping(paymentForm as PaymentsPaymentForm, inputInvoice, focus).addEventListener('finish', ({shippingAddress, requestedInfo}) => {
PopupElement.createPopup(PopupPaymentShipping, paymentForm as PaymentsPaymentForm, inputInvoice, focus).addEventListener('finish', ({shippingAddress, requestedInfo}) => {
lastRequestedInfo = requestedInfo;
savedInfo = (paymentForm as PaymentsPaymentForm).saved_info = shippingAddress;
setShippingInfo(shippingAddress);
@ -643,7 +643,7 @@ export default class PopupPayment extends PopupElement {
icon: 'shipping',
titleLangKey: 'PaymentCheckoutShippingMethod',
clickable: !isReceipt && (onShippingMethodClick = () => {
new PopupPaymentShippingMethods(paymentForm as PaymentsPaymentForm, lastRequestedInfo, lastShippingOption).addEventListener('finish', (shippingOption) => {
PopupElement.createPopup(PopupPaymentShippingMethods, paymentForm as PaymentsPaymentForm, lastRequestedInfo, lastShippingOption).addEventListener('finish', (shippingOption) => {
setShippingOption(shippingOption);
});
})
@ -736,7 +736,7 @@ export default class PopupPayment extends PopupElement {
}
Promise.resolve(passwordState ?? this.managers.passwordManager.getState()).then((_passwordState) => {
new PopupPaymentCardConfirmation(savedCredentials.title, _passwordState).addEventListener('finish', (tmpPassword) => {
PopupElement.createPopup(PopupPaymentCardConfirmation, savedCredentials.title, _passwordState).addEventListener('finish', (tmpPassword) => {
passwordState = undefined;
lastTmpPasword = tmpPassword;
simulateClickEvent(payButton);
@ -783,7 +783,7 @@ export default class PopupPayment extends PopupElement {
if(paymentResult._ === 'payments.paymentResult') {
onConfirmed();
} else {
popupPaymentVerification = new PopupPaymentVerification(paymentResult.url, !mediaInvoice.extended_media);
popupPaymentVerification = PopupElement.createPopup(PopupPaymentVerification, paymentResult.url, !mediaInvoice.extended_media);
popupPaymentVerification.addEventListener('finish', () => {
popupPaymentVerification = undefined;

View File

@ -4,6 +4,7 @@
* https://github.com/morethanwords/tweb/blob/master/LICENSE
*/
import PopupElement from '.';
import {attachClickEvent} from '../../helpers/dom/clickEvent';
import findUpClassName from '../../helpers/dom/findUpClassName';
import whichChild from '../../helpers/dom/whichChild';
@ -45,7 +46,7 @@ export default class PopupReportMessages extends PopupPeer {
preloadStickerPromise.then(() => {
this.hide();
new PopupReportMessagesConfirm(peerId, mids, reason, onConfirm);
PopupElement.createPopup(PopupReportMessagesConfirm, peerId, mids, reason, onConfirm);
});
}, {listenerSetter: this.listenerSetter});

View File

@ -110,7 +110,7 @@ export default class PopupPinMessage {
addCancelButton(buttons);
const popup = new PopupPeer('popup-delete-chat', {
const popup = PopupElement.createPopup(PopupPeer, 'popup-delete-chat', {
peerId,
titleLangKey: title,
descriptionLangKey: description,

View File

@ -33,7 +33,6 @@ export type RowMediaSizeType = 'small' | 'medium' | 'big' | 'abitbigger' | 'bigg
export default class Row {
public container: HTMLElement;
public title: HTMLElement;
public titleRow: HTMLElement;
public titleRight: HTMLElement;
public media: HTMLElement;
@ -48,6 +47,7 @@ export default class Row {
public buttonRight: HTMLElement;
private _title: HTMLElement;
private _subtitle: HTMLElement;
private _midtitle: HTMLElement;
@ -127,6 +127,9 @@ export default class Row {
options.titleRight = this.checkboxField.label;
} else {
havePadding = true;
if(!this.checkboxField.span) {
this.checkboxField.label.classList.add('checkbox-field-absolute');
}
this.container.append(this.checkboxField.label);
}
@ -144,7 +147,7 @@ export default class Row {
i.label.classList.add('disable-hover');
}
if(options.title || options.titleLangKey) {
if(options.title || options.titleLangKey || options.titleRight || options.titleRightSecondary) {
let c: HTMLElement;
const titleRightContent = options.titleRight || options.titleRightSecondary;
if(titleRightContent) {
@ -154,7 +157,7 @@ export default class Row {
c = this.container;
}
this.title = this.createTitle();
this._title = this.createTitle();
if(options.noWrap) this.title.classList.add('no-wrap');
if(options.title) {
setContent(this.title, options.title);
@ -227,6 +230,10 @@ export default class Row {
}
}
public get title() {
return this._title;
}
public get subtitle() {
return this._subtitle ??= this.createSubtitle();
}

View File

@ -288,6 +288,7 @@ export class AppSidebarLeft extends SidebarSlider {
}]
});
this.newBtnMenu.className = 'btn-circle rp btn-corner z-depth-1 btn-menu-toggle animated-button-icon';
this.newBtnMenu.tabIndex = -1;
this.newBtnMenu.insertAdjacentHTML('afterbegin', `
<span class="tgico tgico-newchat_filled"></span>
<span class="tgico tgico-close"></span>
@ -297,6 +298,7 @@ export class AppSidebarLeft extends SidebarSlider {
this.updateBtn = document.createElement('div');
this.updateBtn.className = 'btn-circle rp btn-corner z-depth-1 btn-update is-hidden';
this.updateBtn.tabIndex = -1;
ripple(this.updateBtn);
this.updateBtn.append(i18n('Update'));

View File

@ -18,6 +18,7 @@ import {attachClickEvent} from '../../../../helpers/dom/clickEvent';
import matchEmail from '../../../../lib/richTextProcessor/matchEmail';
import wrapStickerEmoji from '../../../wrappers/stickerEmoji';
import SettingSection from '../../../settingSection';
import PopupElement from '../../../popups';
export default class AppTwoStepVerificationEmailTab extends SliderSuperTab {
public inputField: InputField;
@ -125,7 +126,7 @@ export default class AppTwoStepVerificationEmailTab extends SliderSuperTab {
};
attachClickEvent(btnSkip, (e) => {
const popup = new PopupPeer('popup-skip-email', {
const popup = PopupElement.createPopup(PopupPeer, 'popup-skip-email', {
buttons: [{
langKey: 'Cancel',
isCancel: true

View File

@ -8,6 +8,7 @@ import {attachClickEvent} from '../../../../helpers/dom/clickEvent';
import {AccountPassword} from '../../../../layer';
import {_i18n} from '../../../../lib/langPack';
import Button from '../../../button';
import PopupElement from '../../../popups';
import PopupPeer from '../../../popups/peer';
import SettingSection from '../../../settingSection';
import {SliderSuperTab} from '../../../slider';
@ -57,7 +58,7 @@ export default class AppTwoStepVerificationTab extends SliderSuperTab {
});
attachClickEvent(btnDisablePassword, () => {
const popup = new PopupPeer('popup-disable-password', {
const popup = PopupElement.createPopup(PopupPeer, 'popup-disable-password', {
buttons: [{
langKey: 'Disable',
callback: () => {

View File

@ -21,6 +21,7 @@ import {attachContextMenuListener} from '../../../helpers/dom/attachContextMenuL
import positionMenu from '../../../helpers/positionMenu';
import contextMenuController from '../../../helpers/contextMenuController';
import SettingSection from '../../settingSection';
import PopupElement from '../../popups';
export default class AppActiveSessionsTab extends SliderSuperTabEventable {
public authorizations: Authorization.authorization[];
@ -61,7 +62,7 @@ export default class AppActiveSessionsTab extends SliderSuperTabEventable {
if(authorizations.length) {
const btnTerminate = Button('btn-primary btn-transparent danger', {icon: 'stop', text: 'TerminateAllSessions'});
attachClickEvent(btnTerminate, (e) => {
new PopupPeer('revoke-session', {
PopupElement.createPopup(PopupPeer, 'revoke-session', {
buttons: [{
langKey: 'Terminate',
isDanger: true,
@ -112,7 +113,7 @@ export default class AppActiveSessionsTab extends SliderSuperTabEventable {
const onTerminateClick = () => {
const hash = target.dataset.hash;
new PopupPeer('revoke-session', {
PopupElement.createPopup(PopupPeer, 'revoke-session', {
buttons: [{
langKey: 'Terminate',
isDanger: true,

View File

@ -19,6 +19,7 @@ import positionMenu from '../../../helpers/positionMenu';
import contextMenuController from '../../../helpers/contextMenuController';
import getPeerActiveUsernames from '../../../lib/appManagers/utils/peers/getPeerActiveUsernames';
import SettingSection from '../../settingSection';
import PopupElement from '../../popups';
export default class AppBlockedUsersTab extends SliderSuperTab {
public peerIds: PeerId[];
@ -40,7 +41,7 @@ export default class AppBlockedUsersTab extends SliderSuperTab {
this.content.append(btnAdd);
attachClickEvent(btnAdd, (e) => {
new PopupPickUser({
PopupElement.createPopup(PopupPickUser, {
peerTypes: ['contacts'],
placeholder: 'BlockModal.Search.Placeholder',
onSelect: (peerId) => {

View File

@ -24,6 +24,7 @@ import wrapDraftText from '../../../lib/richTextProcessor/wrapDraftText';
import filterAsync from '../../../helpers/array/filterAsync';
import {attachClickEvent} from '../../../helpers/dom/clickEvent';
import SettingSection from '../../settingSection';
import PopupElement from '../../popups';
const MAX_FOLDER_NAME_LENGTH = 12;
@ -66,7 +67,7 @@ export default class AppEditFolderTab extends SliderSuperTab {
icon: 'delete danger',
text: 'FilterMenuDelete',
onClick: () => {
new PopupPeer('filter-delete', {
PopupElement.createPopup(PopupPeer, 'filter-delete', {
titleLangKey: 'ChatList.Filter.Confirm.Remove.Header',
descriptionLangKey: 'ChatList.Filter.Confirm.Remove.Text',
buttons: [{

View File

@ -34,6 +34,7 @@ import {toastNew} from '../../toast';
import AppPrivacyVoicesTab from './privacy/voices';
import SettingSection from '../../settingSection';
import AppActiveWebSessionsTab from './activeWebSessions';
import PopupElement from '../../popups';
export default class AppPrivacyAndSecurityTab extends SliderSuperTabEventable {
private activeSessionsRow: Row;
@ -424,7 +425,7 @@ export default class AppPrivacyAndSecurityTab extends SliderSuperTabEventable {
const section = new SettingSection({name: 'FilterChats'});
const onDeleteClick = () => {
const popup = new PopupPeer('popup-delete-drafts', {
const popup = PopupElement.createPopup(PopupPeer, 'popup-delete-drafts', {
buttons: [{
langKey: 'Delete',
callback: () => {

View File

@ -29,6 +29,7 @@ import PopupElement from '../../popups';
import {attachClickEvent} from '../../../helpers/dom/clickEvent';
import SettingSection from '../../settingSection';
import AppStickersAndEmojiTab from './stickersAndEmoji';
import ButtonCorner from '../../buttonCorner';
export default class AppSettingsTab extends SliderSuperTab {
private buttons: {
@ -58,7 +59,7 @@ export default class AppSettingsTab extends SliderSuperTab {
icon: 'logout',
text: 'EditAccount.Logout',
onClick: () => {
new PopupPeer('logout', {
PopupElement.createPopup(PopupPeer, 'logout', {
titleLangKey: 'LogOut',
descriptionLangKey: 'LogOut.Description',
buttons: [{
@ -82,7 +83,7 @@ export default class AppSettingsTab extends SliderSuperTab {
this.profile.setPeer(rootScope.myId);
const fillPromise = this.profile.fillProfileElements();
const changeAvatarBtn = Button('btn-circle btn-corner z-depth-1 profile-change-avatar', {icon: 'cameraadd'});
const changeAvatarBtn = ButtonCorner({icon: 'cameraadd', className: 'profile-change-avatar'});
attachClickEvent(changeAvatarBtn, () => {
const canvas = document.createElement('canvas');
PopupElement.createPopup(PopupAvatar).open(canvas, (upload) => {

View File

@ -15,6 +15,7 @@ import wrapEmojiText from '../../../lib/richTextProcessor/wrapEmojiText';
import rootScope from '../../../lib/rootScope';
import CheckboxField from '../../checkboxField';
import LazyLoadQueue from '../../lazyLoadQueue';
import PopupElement from '../../popups';
import PopupStickers from '../../popups/stickers';
import Row, {CreateRowFromCheckboxField} from '../../row';
import SettingSection from '../../settingSection';
@ -209,7 +210,7 @@ export default class AppStickersAndEmojiTab extends SliderSuperTab {
subtitleLangArgs: [stickerSet.count],
havePadding: true,
clickable: () => {
new PopupStickers({id: stickerSet.id, access_hash: stickerSet.access_hash}).show();
PopupElement.createPopup(PopupStickers, {id: stickerSet.id, access_hash: stickerSet.access_hash}).show();
},
listenerSetter: this.listenerSetter
});

View File

@ -27,6 +27,7 @@ import getPeerEditableUsername from '../../../lib/appManagers/utils/peers/getPee
import getPeerActiveUsernames from '../../../lib/appManagers/utils/peers/getPeerActiveUsernames';
import {purchaseUsernameCaption} from '../../sidebarLeft/tabs/editProfile';
import confirmationPopup from '../../confirmationPopup';
import PopupElement from '../../popups';
export default class AppChatTypeTab extends SliderSuperTabEventable {
public chatId: ChatId;
@ -102,7 +103,7 @@ export default class AppChatTypeTab extends SliderSuperTabEventable {
const btnRevoke = Button('btn-primary btn-transparent danger', {icon: 'delete', text: 'RevokeLink'});
attachClickEvent(btnRevoke, () => {
new PopupPeer('revoke-link', {
PopupElement.createPopup(PopupPeer, 'revoke-link', {
buttons: [{
langKey: 'RevokeButton',
callback: () => {

View File

@ -24,6 +24,7 @@ import hasRights from '../../../lib/appManagers/utils/chats/hasRights';
import replaceContent from '../../../helpers/dom/replaceContent';
import SettingSection from '../../settingSection';
import getPeerActiveUsernames from '../../../lib/appManagers/utils/peers/getPeerActiveUsernames';
import PopupElement from '../../popups';
export default class AppEditChatTab extends SliderSuperTab {
private chatNameInputField: InputField;
@ -377,7 +378,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(peerId/* , 'delete' */, undefined, (promise) => {
PopupElement.createPopup(PopupDeleteDialog, peerId/* , 'delete' */, undefined, (promise) => {
const toggle = toggleDisability([btnDelete], true);
promise.then(() => {
this.close();

View File

@ -13,7 +13,7 @@ import Button from '../../button';
import PeerTitle from '../../peerTitle';
import rootScope from '../../../lib/rootScope';
import PopupPeer from '../../popups/peer';
import {addCancelButton} from '../../popups';
import PopupElement, {addCancelButton} from '../../popups';
import {i18n} from '../../../lib/langPack';
import {attachClickEvent} from '../../../helpers/dom/clickEvent';
import toggleDisability from '../../../helpers/dom/toggleDisability';
@ -160,7 +160,7 @@ export default class AppEditContactTab extends SliderSuperTab {
const btnDelete = Button('btn-primary btn-transparent danger', {icon: 'delete', text: 'PeerInfo.DeleteContact'});
attachClickEvent(btnDelete, () => {
new PopupPeer('popup-delete-contact', {
PopupElement.createPopup(PopupPeer, 'popup-delete-contact', {
peerId: peerId,
titleLangKey: 'DeleteContact',
descriptionLangKey: 'AreYouSureDeleteContact',

View File

@ -26,6 +26,7 @@ import {SliderSuperTabEventable} from '../../sliderTab';
import {toast} from '../../toast';
import AppUserPermissionsTab from './userPermissions';
import CheckboxFields, {CheckboxFieldsField} from '../../checkboxFields';
import PopupElement from '../../popups';
type PermissionsCheckboxFieldsField = CheckboxFieldsField & {
flags: ChatRights[],
@ -186,7 +187,7 @@ export default class AppGroupPermissionsTab extends SliderSuperTabEventable {
subtitleLangKey: 'Loading',
icon: 'adduser',
clickable: () => {
new PopupPickUser({
PopupElement.createPopup(PopupPickUser, {
peerTypes: ['channelParticipants'],
onSelect: (peerId) => {
setTimeout(() => {

View File

@ -25,6 +25,7 @@ import {Message} from '../../../layer';
import getMessageThreadId from '../../../lib/appManagers/utils/messages/getMessageThreadId';
import AppEditTopicTab from './editTopic';
import liteMode from '../../../helpers/liteMode';
import PopupElement from '../../popups';
type SharedMediaHistoryStorage = Partial<{
[type in SearchSuperType]: {mid: number, peerId: PeerId}[]
@ -296,7 +297,7 @@ export default class AppSharedMediaTab extends SliderSuperTab {
peerId
}).element);
new PopupPeer('popup-add-members', {
PopupElement.createPopup(PopupPeer, 'popup-add-members', {
peerId,
titleLangKey,
descriptionLangKey,
@ -333,7 +334,7 @@ export default class AppSharedMediaTab extends SliderSuperTab {
placeholder: 'SendMessageTo'
});
} else {
new PopupPickUser({
PopupElement.createPopup(PopupPickUser, {
peerTypes: ['contacts'],
placeholder: 'Search',
onSelect: (peerId) => {

View File

@ -20,6 +20,7 @@ import setInnerHTML from '../../../helpers/dom/setInnerHTML';
import wrapEmojiText from '../../../lib/richTextProcessor/wrapEmojiText';
import attachStickerViewerListeners from '../../stickerViewer';
import wrapSticker from '../../wrappers/sticker';
import PopupElement from '../../popups';
export default class AppStickersTab extends SliderSuperTab {
private inputSearch: InputSearch;
@ -79,7 +80,7 @@ export default class AppStickersTab extends SliderSuperTab {
});
} else {
this.managers.appStickersManager.getStickerSet({id, access_hash}).then((full) => {
new PopupStickers(full.set).show();
PopupElement.createPopup(PopupStickers, full.set).show();
});
}
}, {listenerSetter: this.listenerSetter});

View File

@ -263,14 +263,14 @@ export default class TopbarCall {
return;
}
new PopupGroupCall().show();
PopupElement.createPopup(PopupGroupCall).show();
} else if(this.instance instanceof CallInstance) {
const popups = PopupElement.getPopups(PopupCall);
if(popups.find((popup) => popup.getCallInstance() === this.instance)) {
return;
}
new PopupCall(this.instance).show();
PopupElement.createPopup(PopupCall, this.instance).show();
}
}, {listenerSetter});

View File

@ -47,6 +47,7 @@ import wrapStickerAnimation from './stickerAnimation';
import framesCache from '../../helpers/framesCache';
import {IS_MOBILE} from '../../environment/userAgent';
import liteMode, {LiteModeKey} from '../../helpers/liteMode';
import PopupElement from '../popups';
// https://github.com/telegramdesktop/tdesktop/blob/master/Telegram/SourceFiles/history/view/media/history_view_sticker.cpp#L40
export const STICKER_EFFECT_MULTIPLIER = 1 + 0.245 * 2;
@ -699,7 +700,7 @@ function attachStickerEffectHandler({container, doc, managers, middleware, isOut
const a = document.createElement('a');
a.onclick = () => {
hideToast();
new PopupStickers(doc.stickerSetInput).show();
PopupElement.createPopup(PopupStickers, doc.stickerSetInput).show();
};
toastNew({
@ -782,7 +783,7 @@ export async function onEmojiStickerClick({event, container, managers, peerId, m
}
const activeAnimations: Set<{}> = (container as any).activeAnimations ??= new Set();
if(activeAnimations.size >= (IS_MOBILE ? 3 : 5)) {
if(activeAnimations.size >= 3) {
return;
}

View File

@ -21,7 +21,7 @@ const App = {
version: process.env.VERSION,
versionFull: process.env.VERSION_FULL,
build: +process.env.BUILD,
langPackVersion: '1.0.1',
langPackVersion: '1.0.3',
langPack: 'webk',
langPackCode: 'en',
domains: MAIN_DOMAINS,

View File

@ -22,6 +22,10 @@ export default function addAnchorListener<Params extends {pathnameParams?: any,
!options.noCancelEvent && cancelEvent(null);
let href = element.href;
if(!href) {
return;
}
let pathnameParams: any[];
let uriParams: any;

View File

@ -10,7 +10,8 @@ import appImManager from '../../lib/appManagers/appImManager';
import rootScope from '../../lib/rootScope';
import createContextMenu from './createContextMenu';
import findUpClassName from './findUpClassName';
import emoticonsDropdown, {EmoticonsDropdown} from '../../components/emoticonsDropdown';
import {EmoticonsDropdown} from '../../components/emoticonsDropdown';
import PopupElement from '../../components/popups';
export default function createStickersContextMenu(options: {
listenTo: HTMLElement,
@ -45,7 +46,7 @@ export default function createStickersContextMenu(options: {
buttons: [{
icon: 'stickers',
text: 'Context.ViewStickerSet',
onClick: () => new PopupStickers(doc.stickerSetInput).show(),
onClick: () => PopupElement.createPopup(PopupStickers, doc.stickerSetInput).show(),
verify: () => !isStickerPack
}, {
icon: 'favourites',

View File

@ -25,7 +25,12 @@ function number_format(number: any, decimals: any, dec_point: any, thousands_sep
return s.join(dec);
}
export default function paymentsWrapCurrencyAmount(amount: number | string, currency: string, skipSymbol?: boolean) {
export default function paymentsWrapCurrencyAmount(
amount: number | string,
currency: string,
skipSymbol?: boolean,
useNative?: boolean
) {
amount = +amount;
const isNegative = amount < 0;
@ -47,7 +52,7 @@ export default function paymentsWrapCurrencyAmount(amount: number | string, curr
return formatted;
}
let symbol = currencyData.symbol;
let symbol = useNative ? currencyData.native || currencyData.symbol : currencyData.symbol;
if(isNegative && !currencyData.space_between && currencyData.symbol_left) {
symbol = '-' + symbol;
formatted = formatted.replace('-', '');

View File

@ -940,6 +940,11 @@ const lang = {
'SuggestStickersNone': 'None',
'DynamicPackOrder': 'Dynamic Pack Order',
'DynamicPackOrderInfo': 'Automatically place recently used sticker packs at the front of the panel.',
'GiftPremium': 'Gift Premium',
'GiftTelegramPremiumTitle': 'Gift Telegram Premium',
'GiftTelegramPremiumDescription': 'Give **%1$s** access to exclusive features with **Telegram Premium**.',
'PricePerMonth': '%1$s / month',
'GiftSubscriptionFor': 'Gift Subscription for %1$s',
// * macos
'AccountSettings.Filters': 'Chat Folders',

View File

@ -107,6 +107,7 @@ import partition from '../../helpers/array/partition';
import indexOfAndSplice from '../../helpers/array/indexOfAndSplice';
import liteMode, {LiteModeKey} from '../../helpers/liteMode';
import RLottiePlayer from '../rlottie/rlottiePlayer';
import PopupGiftPremium from '../../components/popups/giftPremium';
export type ChatSavedPosition = {
mids: number[],
@ -345,7 +346,7 @@ export class AppImManager extends EventListenerBase<{
const onInstanceDeactivated = (reason: InstanceDeactivateReason) => {
const isUpdated = reason === 'version';
const popup = new PopupElement('popup-instance-deactivated', {overlayClosable: true});
const popup = PopupElement.createPopup(PopupElement, 'popup-instance-deactivated', {overlayClosable: true});
const c = document.createElement('div');
c.classList.add('instance-deactivated-container');
(popup as any).container.replaceWith(c);
@ -530,7 +531,7 @@ export class AppImManager extends EventListenerBase<{
// return;
// }
const popup = new PopupCall(instance);
const popup = PopupElement.createPopup(PopupCall, instance);
instance.addEventListener('acceptCallOverride', () => {
return this.discardCurrentCall(instance.interlocutorUserId.toPeerId(), undefined, instance)
@ -582,7 +583,7 @@ export class AppImManager extends EventListenerBase<{
a.innerText = href;
a.removeAttribute('onclick');
new PopupPeer('popup-masked-url', {
PopupElement.createPopup(PopupPeer, 'popup-masked-url', {
titleLangKey: 'OpenUrlTitle',
descriptionLangKey: 'OpenUrlAlert2',
descriptionLangArgs: [a],
@ -862,11 +863,11 @@ export class AppImManager extends EventListenerBase<{
const share = apiManagerProxy.share;
if(share) {
apiManagerProxy.share = undefined;
new PopupForward(undefined, async(peerId) => {
PopupElement.createPopup(PopupForward, undefined, async(peerId) => {
await this.setPeer({peerId});
if(share.files?.length) {
const foundMedia = share.files.some((file) => MEDIA_MIME_TYPES_SUPPORTED.has(file.type));
new PopupNewMedia(this.chat, share.files, foundMedia ? 'media' : 'document');
PopupElement.createPopup(PopupNewMedia, this.chat, share.files, foundMedia ? 'media' : 'document');
} else {
this.managers.appMessagesManager.sendText(peerId, share.text);
}
@ -1184,7 +1185,7 @@ export class AppImManager extends EventListenerBase<{
case INTERNAL_LINK_TYPE.EMOJI_SET:
case INTERNAL_LINK_TYPE.STICKER_SET: {
new PopupStickers({id: link.set}, link._ === INTERNAL_LINK_TYPE.EMOJI_SET).show();
PopupElement.createPopup(PopupStickers, {id: link.set}, link._ === INTERNAL_LINK_TYPE.EMOJI_SET).show();
break;
}
@ -1204,7 +1205,7 @@ export class AppImManager extends EventListenerBase<{
return;
}
new PopupJoinChatInvite(link.invite, chatInvite);
PopupElement.createPopup(PopupJoinChatInvite, link.invite, chatInvite);
}, (err) => {
if(err.type === 'INVITE_HASH_EXPIRED') {
toast(i18n('InviteExpired'));
@ -1252,7 +1253,7 @@ export class AppImManager extends EventListenerBase<{
// }
// };
new PopupPayment(undefined, inputInvoice, paymentForm);
PopupElement.createPopup(PopupPayment, undefined, inputInvoice, paymentForm);
});
});
break;
@ -2546,6 +2547,12 @@ export class AppImManager extends EventListenerBase<{
options1.threadId === options2.threadId &&
(typeof(options1.type) !== typeof(options2.type) || options1.type === options2.type);
}
public giftPremium(peerId: PeerId) {
this.managers.appProfileManager.getProfile(peerId.toUserId()).then((profile) => {
PopupElement.createPopup(PopupGiftPremium, peerId, profile.premium_gifts);
});
}
}
const appImManager = new AppImManager();

View File

@ -5904,18 +5904,54 @@ export class AppMessagesManager extends AppManager {
});
}
public isHistoryResultEnd(historyResult: Exclude<MessagesMessages, MessagesMessages.messagesMessagesNotModified>, limit: number, add_offset: number) {
public isHistoryResultEnd(
historyResult: Exclude<MessagesMessages, MessagesMessages.messagesMessagesNotModified>,
limit: number,
add_offset: number,
offset_id: number
) {
const {offset_id_offset, messages} = historyResult as MessagesMessages.messagesMessagesSlice;
const mids = messages.map((message) => {
return (message as MyMessage).mid;
});
const count = (historyResult as MessagesMessages.messagesMessagesSlice).count || messages.length;
const offsetIdOffset = offset_id_offset ?? count - 1;
const topWasMeantToLoad = add_offset < 0 ? limit + add_offset : limit;
const bottomWasMeantToLoad = Math.abs(add_offset);
const isTopEnd = offsetIdOffset >= (count - topWasMeantToLoad) || count < topWasMeantToLoad;
const isBottomEnd = !offsetIdOffset || (add_offset < 0 && (offsetIdOffset + add_offset) <= 0);
let offsetIdOffset = offset_id_offset;
let isTopEnd = false, isBottomEnd = false;
return {count, offsetIdOffset, isTopEnd, isBottomEnd};
// if(offsetIdOffset === undefined && !bottomWasMeantToLoad) {
// offsetIdOffset = 0;
// }
if(offsetIdOffset !== undefined) {
isTopEnd = offsetIdOffset >= (count - topWasMeantToLoad) || count < topWasMeantToLoad;
isBottomEnd = !offsetIdOffset || (add_offset < 0 && (offsetIdOffset + add_offset) <= 0);
} else if(offset_id && getServerMessageId(offset_id)) {
let i = 0;
for(const length = mids.length; i < length; ++i) {
if(offset_id > mids[i]) {
break;
}
}
const topLoaded = messages.length - i;
const bottomLoaded = mids.includes(offset_id) ? i - 1 : i;
if(topWasMeantToLoad) isTopEnd = topLoaded < topWasMeantToLoad;
if(bottomWasMeantToLoad) isBottomEnd = bottomLoaded < bottomWasMeantToLoad;
if(isTopEnd || isBottomEnd) {
offsetIdOffset = isTopEnd ? count - topLoaded : bottomLoaded;
}
}
offsetIdOffset ??= 0;
return {count, offsetIdOffset, isTopEnd, isBottomEnd, mids};
}
public mergeHistoryResult(
@ -5926,11 +5962,8 @@ export class AppMessagesManager extends AppManager {
add_offset: number
) {
const {messages} = historyResult as MessagesMessages.messagesMessagesSlice;
const isEnd = this.isHistoryResultEnd(historyResult, limit, add_offset);
const {count, offsetIdOffset, isTopEnd, isBottomEnd} = isEnd;
const mids = messages.map((message) => {
return (message as MyMessage).mid;
});
const isEnd = this.isHistoryResultEnd(historyResult, limit, add_offset, offset_id);
const {count, offsetIdOffset, isTopEnd, isBottomEnd, mids} = isEnd;
// * add bound manually.
// * offset_id will be inclusive only if there is 'add_offset' <= -1 (-1 - will only include the 'offset_id')

View File

@ -22,6 +22,7 @@ import {ReferenceContext} from '../mtproto/referenceDatabase';
import generateMessageId from './utils/messageId/generateMessageId';
import assumeType from '../../helpers/assumeType';
import makeError from '../../helpers/makeError';
import callbackify from '../../helpers/callbackify';
export type UserTyping = Partial<{userId: UserId, action: SendMessageAction, timeout: number}>;
@ -669,6 +670,18 @@ export class AppProfileManager extends AppManager {
return this.typingsInPeer[this.getTypingsKey(peerId, threadId)];
}
public canGiftPremium(userId: UserId) {
const user = this.appUsersManager.getUser(userId);
if(user?.pFlags?.premium || true) {
return false;
}
return callbackify(this.getProfile(userId), (userFull) => {
const user = this.appUsersManager.getUser(userId);
return !!userFull.premium_gifts && !user?.pFlags?.premium;
});
}
private onUpdateChatParticipants = (update: Update.updateChatParticipants) => {
const participants = update.participants;
if(participants._ !== 'chatParticipants') {

View File

@ -1,6 +1,7 @@
import {Document, Message, MessageMedia} from '../../../../layer';
export default function getMediaDurationFromMessage(message: Message.message) {
if(!message) return undefined;
const doc = (message.media as MessageMedia.messageMediaDocument)?.document as Document.document;
const duration = ((['voice', 'audio', 'video'] as Document.document['type'][]).includes(doc?.type) && doc.duration) || undefined;
return duration;

View File

@ -1475,6 +1475,10 @@ export default function wrapRichText(text: string, options: Partial<{
(element as HTMLAnchorElement).href = '#';
element.setAttribute('onclick', 'setMediaTimestamp(this)');
if(options.maxMediaTimestamp === Infinity) {
element.classList.add('is-disabled');
}
break;
}
}

View File

@ -686,6 +686,31 @@ $btn-menu-z-index: 4;
pointer-events: none !important;
opacity: var(--disabled-opacity);
}
&.shimmer {
&:before {
content: "";
position: absolute;
top: 0;
display: block;
width: 100%;
height: 100%;
background: linear-gradient(to right, transparent 0%, rgba(var(--surface-color-rgb), .2) 50%, transparent 100%);
animation: wave 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
@keyframes wave {
0% {
transform: translateX(-100%);
}
50% {
transform: translateX(100%);
}
100% {
transform: translateX(100%);
}
}
}
}
}
.btn-control {

View File

@ -2475,6 +2475,12 @@ $bubble-border-radius-big: 12px;
// }
}
.timestamp.is-disabled {
color: inherit;
text-decoration: none !important;
cursor: inherit;
}
@keyframes audio-dots {
0% {
content: "";

View File

@ -254,6 +254,19 @@ $row-border-radius: $border-radius-medium;
margin-inline: .125rem .125rem;
padding: 0;
}
&-absolute {
position: absolute;
margin: 0 !important;
padding: 0 !important;
left: 0;
}
&-round {
.checkbox-box-border {
z-index: unset;
}
}
}
&-subtitle {

View File

@ -0,0 +1,103 @@
/*
* https://github.com/morethanwords/tweb
* Copyright (C) 2019-2021 Eduard Kuzmenko
* https://github.com/morethanwords/tweb/blob/master/LICENSE
*/
.popup-gift-premium {
$parent: ".popup";
#{$parent} {
&-container {
padding: 0;
width: 26.25rem;
max-width: 26.25rem;
max-height: unquote('min(100%, 43.5rem)');
border-radius: $border-radius-huge;
}
&-header {
height: 3.5rem;
margin: 0;
padding: 0 1rem;
margin-bottom: -2rem;
}
}
.scrollable-y {
flex: 1 1 auto;
padding: 0 1rem 1rem;
}
&-avatar {
display: block;
margin: 0 auto;
}
&-title,
&-subtitle {
text-align: center;
display: block;
}
&-title {
font-size: var(--font-size-20);
font-weight: var(--font-weight-bold);
margin: .75rem 0;
}
&-options {
display: flex;
flex-direction: column;
margin: .5rem 0 1rem;
}
&-option {
margin-top: .5rem;
&:nth-child(1) {
--primary-color: #C564F3;
}
&:nth-child(2) {
--primary-color: #AC64F3;
}
&:nth-child(3) {
--primary-color: #9377FF;
}
}
&-discount {
background-color: var(--primary-color);
border-radius: 6px;
color: #fff;
margin-right: .375rem;
padding: 0 0.3125rem;
height: 20px;
display: inline-block;
line-height: 20px;
}
&-confirm {
--ripple-color: rgba(255, 255, 255, #{$hover-alpha});
background: linear-gradient(88.39deg, #6C93FF -2.56%, #976FFF 51.27%, #DF69D1 107.39%) !important;
font-weight: var(--font-weight-bold);
color: #fff;
text-transform: uppercase;
@include hover() {
&:after {
content: " ";
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
background-color: #fff;
opacity: $hover-alpha;
}
}
}
}

View File

@ -119,6 +119,12 @@
.scrollable {
position: relative;
}
.scrollable-y-bordered {
&:last-child {
border-bottom: none;
}
}
}
&-buttons {

View File

@ -411,6 +411,7 @@ $chat-input-inner-padding-handhelds: .25rem;
@import "partials/popups/paymentVerification";
@import "partials/popups/paymentCardConfirmation";
@import "partials/popups/limit";
@import "partials/popups/giftPremium";
@import "partials/pages/pages";
@import "partials/pages/authCode";

View File

@ -3,9 +3,9 @@
@font-face {
font-family: '#{$tgico-font-family}';
src:
url('#{$tgico-font-path}/#{$tgico-font-family}.ttf?2fcrrv') format('truetype'),
url('#{$tgico-font-path}/#{$tgico-font-family}.woff?2fcrrv') format('woff'),
url('#{$tgico-font-path}/#{$tgico-font-family}.svg?2fcrrv##{$tgico-font-family}') format('svg');
url('#{$tgico-font-path}/#{$tgico-font-family}.ttf?bv435t') format('truetype'),
url('#{$tgico-font-path}/#{$tgico-font-family}.woff?bv435t') format('woff'),
url('#{$tgico-font-path}/#{$tgico-font-family}.svg?bv435t##{$tgico-font-family}') format('svg');
font-weight: normal;
font-style: normal;
font-display: block;
@ -432,6 +432,11 @@
content: $tgico-gifs;
}
}
.tgico-gift {
&:before {
content: $tgico-gift;
}
}
.tgico-group {
&:before {
content: $tgico-group;

View File

@ -78,135 +78,136 @@ $tgico-fullscreen: "\e94c";
$tgico-gc_microphone: "\e94d";
$tgico-gc_microphoneoff: "\e94e";
$tgico-gifs: "\e94f";
$tgico-group: "\e950";
$tgico-groupmedia: "\e951";
$tgico-groupmediaoff: "\e952";
$tgico-help: "\e953";
$tgico-hide: "\e954";
$tgico-image: "\e955";
$tgico-info: "\e956";
$tgico-info2: "\e957";
$tgico-italic: "\e958";
$tgico-keyboard: "\e959";
$tgico-lamp: "\e95a";
$tgico-language: "\e95b";
$tgico-largepause: "\e95c";
$tgico-largeplay: "\e95d";
$tgico-left: "\e95e";
$tgico-limit_chat: "\e95f";
$tgico-limit_chats: "\e960";
$tgico-limit_file: "\e961";
$tgico-limit_folders: "\e962";
$tgico-limit_link: "\e963";
$tgico-limit_pin: "\e964";
$tgico-link: "\e965";
$tgico-listscreenshare: "\e966";
$tgico-livelocation: "\e967";
$tgico-location: "\e968";
$tgico-lock: "\e969";
$tgico-lockoff: "\e96a";
$tgico-loginlogodesktop: "\e96b";
$tgico-loginlogomobile: "\e96c";
$tgico-logout: "\e96d";
$tgico-mediaspoiler: "\e96e";
$tgico-mediaspoileroff: "\e96f";
$tgico-mention: "\e970";
$tgico-menu: "\e971";
$tgico-message: "\e972";
$tgico-messageunread: "\e973";
$tgico-microphone: "\e974";
$tgico-microphone_crossed: "\e975";
$tgico-microphone_crossed_filled: "\e976";
$tgico-microphone_filled: "\e977";
$tgico-minus: "\e978";
$tgico-monospace: "\e979";
$tgico-more: "\e97a";
$tgico-mute: "\e97b";
$tgico-muted: "\e97c";
$tgico-newchannel: "\e97d";
$tgico-newchat_filled: "\e97e";
$tgico-newgroup: "\e97f";
$tgico-newprivate: "\e980";
$tgico-next: "\e981";
$tgico-noncontacts: "\e982";
$tgico-nosound: "\e983";
$tgico-passwordoff: "\e984";
$tgico-pause: "\e985";
$tgico-permissions: "\e986";
$tgico-phone: "\e987";
$tgico-pin: "\e988";
$tgico-pinlist: "\e989";
$tgico-pinned_filled: "\e98a";
$tgico-pinnedchat: "\e98b";
$tgico-pip: "\e98c";
$tgico-play: "\e98d";
$tgico-playback_05: "\e98e";
$tgico-playback_15: "\e98f";
$tgico-playback_1x: "\e990";
$tgico-playback_2x: "\e991";
$tgico-plus: "\e992";
$tgico-poll: "\e993";
$tgico-premium_addone: "\e994";
$tgico-premium_double: "\e995";
$tgico-premium_lock: "\e996";
$tgico-premium_unlock: "\e997";
$tgico-previous: "\e998";
$tgico-radiooff: "\e999";
$tgico-radioon: "\e99a";
$tgico-reactions: "\e99b";
$tgico-readchats: "\e99c";
$tgico-recent: "\e99d";
$tgico-replace: "\e99e";
$tgico-reply: "\e99f";
$tgico-reply_filled: "\e9a0";
$tgico-rightpanel: "\e9a1";
$tgico-rotate_left: "\e9a2";
$tgico-rotate_right: "\e9a3";
$tgico-saved: "\e9a4";
$tgico-savedmessages: "\e9a5";
$tgico-schedule: "\e9a6";
$tgico-scheduled: "\e9a7";
$tgico-search: "\e9a8";
$tgico-select: "\e9a9";
$tgico-send: "\e9aa";
$tgico-send2: "\e9ab";
$tgico-sending: "\e9ac";
$tgico-sendingerror: "\e9ad";
$tgico-settings: "\e9ae";
$tgico-settings_filled: "\e9af";
$tgico-sharescreen_filled: "\e9b0";
$tgico-shipping: "\e9b1";
$tgico-shuffle: "\e9b2";
$tgico-smallscreen: "\e9b3";
$tgico-smile: "\e9b4";
$tgico-spoiler: "\e9b5";
$tgico-sport: "\e9b6";
$tgico-star: "\e9b7";
$tgico-stickers: "\e9b8";
$tgico-stickers_face: "\e9b9";
$tgico-stop: "\e9ba";
$tgico-strikethrough: "\e9bb";
$tgico-textedit: "\e9bc";
$tgico-tip: "\e9bd";
$tgico-tools: "\e9be";
$tgico-topics: "\e9bf";
$tgico-transcribe: "\e9c0";
$tgico-unarchive: "\e9c1";
$tgico-underline: "\e9c2";
$tgico-unmute: "\e9c3";
$tgico-unpin: "\e9c4";
$tgico-unread: "\e9c5";
$tgico-up: "\e9c6";
$tgico-user: "\e9c7";
$tgico-username: "\e9c8";
$tgico-videocamera: "\e9c9";
$tgico-videocamera_crossed_filled: "\e9ca";
$tgico-videocamera_filled: "\e9cb";
$tgico-videochat: "\e9cc";
$tgico-volume_down: "\e9cd";
$tgico-volume_mute: "\e9ce";
$tgico-volume_off: "\e9cf";
$tgico-volume_up: "\e9d0";
$tgico-zoomin: "\e9d1";
$tgico-zoomout: "\e9d2";
$tgico-gift: "\e950";
$tgico-group: "\e951";
$tgico-groupmedia: "\e952";
$tgico-groupmediaoff: "\e953";
$tgico-help: "\e954";
$tgico-hide: "\e955";
$tgico-image: "\e956";
$tgico-info: "\e957";
$tgico-info2: "\e958";
$tgico-italic: "\e959";
$tgico-keyboard: "\e95a";
$tgico-lamp: "\e95b";
$tgico-language: "\e95c";
$tgico-largepause: "\e95d";
$tgico-largeplay: "\e95e";
$tgico-left: "\e95f";
$tgico-limit_chat: "\e960";
$tgico-limit_chats: "\e961";
$tgico-limit_file: "\e962";
$tgico-limit_folders: "\e963";
$tgico-limit_link: "\e964";
$tgico-limit_pin: "\e965";
$tgico-link: "\e966";
$tgico-listscreenshare: "\e967";
$tgico-livelocation: "\e968";
$tgico-location: "\e969";
$tgico-lock: "\e96a";
$tgico-lockoff: "\e96b";
$tgico-loginlogodesktop: "\e96c";
$tgico-loginlogomobile: "\e96d";
$tgico-logout: "\e96e";
$tgico-mediaspoiler: "\e96f";
$tgico-mediaspoileroff: "\e970";
$tgico-mention: "\e971";
$tgico-menu: "\e972";
$tgico-message: "\e973";
$tgico-messageunread: "\e974";
$tgico-microphone: "\e975";
$tgico-microphone_crossed: "\e976";
$tgico-microphone_crossed_filled: "\e977";
$tgico-microphone_filled: "\e978";
$tgico-minus: "\e979";
$tgico-monospace: "\e97a";
$tgico-more: "\e97b";
$tgico-mute: "\e97c";
$tgico-muted: "\e97d";
$tgico-newchannel: "\e97e";
$tgico-newchat_filled: "\e97f";
$tgico-newgroup: "\e980";
$tgico-newprivate: "\e981";
$tgico-next: "\e982";
$tgico-noncontacts: "\e983";
$tgico-nosound: "\e984";
$tgico-passwordoff: "\e985";
$tgico-pause: "\e986";
$tgico-permissions: "\e987";
$tgico-phone: "\e988";
$tgico-pin: "\e989";
$tgico-pinlist: "\e98a";
$tgico-pinned_filled: "\e98b";
$tgico-pinnedchat: "\e98c";
$tgico-pip: "\e98d";
$tgico-play: "\e98e";
$tgico-playback_05: "\e98f";
$tgico-playback_15: "\e990";
$tgico-playback_1x: "\e991";
$tgico-playback_2x: "\e992";
$tgico-plus: "\e993";
$tgico-poll: "\e994";
$tgico-premium_addone: "\e995";
$tgico-premium_double: "\e996";
$tgico-premium_lock: "\e997";
$tgico-premium_unlock: "\e998";
$tgico-previous: "\e999";
$tgico-radiooff: "\e99a";
$tgico-radioon: "\e99b";
$tgico-reactions: "\e99c";
$tgico-readchats: "\e99d";
$tgico-recent: "\e99e";
$tgico-replace: "\e99f";
$tgico-reply: "\e9a0";
$tgico-reply_filled: "\e9a1";
$tgico-rightpanel: "\e9a2";
$tgico-rotate_left: "\e9a3";
$tgico-rotate_right: "\e9a4";
$tgico-saved: "\e9a5";
$tgico-savedmessages: "\e9a6";
$tgico-schedule: "\e9a7";
$tgico-scheduled: "\e9a8";
$tgico-search: "\e9a9";
$tgico-select: "\e9aa";
$tgico-send: "\e9ab";
$tgico-send2: "\e9ac";
$tgico-sending: "\e9ad";
$tgico-sendingerror: "\e9ae";
$tgico-settings: "\e9af";
$tgico-settings_filled: "\e9b0";
$tgico-sharescreen_filled: "\e9b1";
$tgico-shipping: "\e9b2";
$tgico-shuffle: "\e9b3";
$tgico-smallscreen: "\e9b4";
$tgico-smile: "\e9b5";
$tgico-spoiler: "\e9b6";
$tgico-sport: "\e9b7";
$tgico-star: "\e9b8";
$tgico-stickers: "\e9b9";
$tgico-stickers_face: "\e9ba";
$tgico-stop: "\e9bb";
$tgico-strikethrough: "\e9bc";
$tgico-textedit: "\e9bd";
$tgico-tip: "\e9be";
$tgico-tools: "\e9bf";
$tgico-topics: "\e9c0";
$tgico-transcribe: "\e9c1";
$tgico-unarchive: "\e9c2";
$tgico-underline: "\e9c3";
$tgico-unmute: "\e9c4";
$tgico-unpin: "\e9c5";
$tgico-unread: "\e9c6";
$tgico-up: "\e9c7";
$tgico-user: "\e9c8";
$tgico-username: "\e9c9";
$tgico-videocamera: "\e9ca";
$tgico-videocamera_crossed_filled: "\e9cb";
$tgico-videocamera_filled: "\e9cc";
$tgico-videochat: "\e9cd";
$tgico-volume_down: "\e9ce";
$tgico-volume_mute: "\e9cf";
$tgico-volume_off: "\e9d0";
$tgico-volume_up: "\e9d1";
$tgico-zoomin: "\e9d2";
$tgico-zoomout: "\e9d3";