Layer 125
Refactor chat deleting More translations Typing text animation
This commit is contained in:
parent
5064741d6f
commit
081ce3e316
@ -19,9 +19,8 @@ const ButtonMenuItem = (options: ButtonMenuItemOptions) => {
|
||||
const {icon, text, onClick} = options;
|
||||
const el = document.createElement('div');
|
||||
el.className = 'btn-menu-item tgico-' + icon;
|
||||
el.append(i18n(text));
|
||||
|
||||
ripple(el);
|
||||
el.append(i18n(text));
|
||||
|
||||
// * cancel keyboard close
|
||||
attachClickEvent(el, CLICK_EVENT_NAME !== 'click' ? (e) => {
|
||||
|
@ -811,7 +811,7 @@ export default class ChatBubbles {
|
||||
|
||||
if(['IMG', 'DIV', "AVATAR-ELEMENT", 'SPAN'/* , 'A' */].indexOf(target.tagName) === -1) target = findUpTag(target, 'DIV');
|
||||
|
||||
if(['DIV', 'SPAN'].indexOf(target.tagName) !== -1/* || target.tagName === 'A' */) {
|
||||
if(['DIV', 'SPAN', 'AVATAR-ELEMENT'].indexOf(target.tagName) !== -1/* || target.tagName === 'A' */) {
|
||||
if(target.classList.contains('goto-original')) {
|
||||
const savedFrom = bubble.dataset.savedFrom;
|
||||
const splitted = savedFrom.split('_');
|
||||
@ -2420,7 +2420,7 @@ export default class ChatBubbles {
|
||||
} else {
|
||||
/* const fromTitle = message.fromId === this.myID || appPeersManager.isBroadcast(message.fwdFromId || message.fromId) ? '' : `<div class="name" data-peer-id="${message.fromId}" style="color: ${appPeersManager.getPeerColorByID(message.fromId, false)};">${appPeersManager.getPeerTitle(message.fromId)}</div>`;
|
||||
nameDiv.innerHTML = fromTitle + 'Forwarded from ' + title; */
|
||||
nameDiv.append('Forwarded from ', title);
|
||||
nameDiv.append(i18n('ForwardedFrom', [title]));
|
||||
|
||||
if(savedFrom) {
|
||||
nameDiv.dataset.savedFrom = savedFrom;
|
||||
|
@ -3,21 +3,23 @@ import { getDeepProperty } from "../helpers/object";
|
||||
import { ripple } from "./ripple";
|
||||
import { LangPackKey, _i18n } from "../lib/langPack";
|
||||
|
||||
export type CheckboxFieldOptions = {
|
||||
text?: LangPackKey,
|
||||
textArgs?: any[],
|
||||
name?: string,
|
||||
round?: boolean,
|
||||
stateKey?: string,
|
||||
disabled?: boolean,
|
||||
checked?: boolean,
|
||||
restriction?: boolean,
|
||||
withRipple?: boolean
|
||||
};
|
||||
export default class CheckboxField {
|
||||
public input: HTMLInputElement;
|
||||
public label: HTMLLabelElement;
|
||||
public span: HTMLSpanElement;
|
||||
|
||||
constructor(options: {
|
||||
text?: LangPackKey,
|
||||
name?: string,
|
||||
round?: boolean,
|
||||
stateKey?: string,
|
||||
disabled?: boolean,
|
||||
checked?: boolean,
|
||||
restriction?: boolean,
|
||||
withRipple?: boolean
|
||||
} = {}) {
|
||||
constructor(options: CheckboxFieldOptions = {}) {
|
||||
const label = this.label = document.createElement('label');
|
||||
label.classList.add('checkbox-field');
|
||||
|
||||
@ -57,7 +59,7 @@ export default class CheckboxField {
|
||||
if(options.text) {
|
||||
span = this.span = document.createElement('span');
|
||||
span.classList.add('checkbox-caption');
|
||||
_i18n(span, options.text);
|
||||
_i18n(span, options.text, options.textArgs);
|
||||
} else {
|
||||
label.classList.add('checkbox-without-caption');
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ import { findUpTag } from "../helpers/dom";
|
||||
import { positionMenu, openBtnMenu } from "./misc";
|
||||
import ButtonMenu, { ButtonMenuItemOptions } from "./buttonMenu";
|
||||
import PopupDeleteDialog from "./popups/deleteDialog";
|
||||
import { i18n } from "../lib/langPack";
|
||||
|
||||
export default class DialogsContextMenu {
|
||||
private element: HTMLElement;
|
||||
@ -150,7 +151,7 @@ export default class DialogsContextMenu {
|
||||
});
|
||||
|
||||
// delete button
|
||||
this.buttons[this.buttons.length - 1].element.firstChild.nodeValue = appPeersManager.getDeleteButtonText(this.selectedId);
|
||||
this.buttons[this.buttons.length - 1].element.lastChild.replaceWith(i18n(appPeersManager.getDeleteButtonText(this.selectedId)));
|
||||
|
||||
li.classList.add('menu-open');
|
||||
positionMenu(e, this.element);
|
||||
|
@ -200,7 +200,7 @@ export default class StickersTab implements EmoticonsTab {
|
||||
|
||||
//console.log('got stickerSet', stickerSet, li);
|
||||
|
||||
if(stickerSet.set.thumb) {
|
||||
if(stickerSet.set.thumbs?.length) {
|
||||
const downloadOptions = appStickersManager.getStickerSetThumbDownloadOptions(stickerSet.set);
|
||||
const promise = appDownloadManager.download(downloadOptions);
|
||||
|
||||
|
@ -2,6 +2,7 @@ import appDownloadManager from "../../lib/appManagers/appDownloadManager";
|
||||
import resizeableImage from "../../lib/cropper";
|
||||
import PopupElement from ".";
|
||||
import { ripple } from "../ripple";
|
||||
import { _i18n } from "../../lib/langPack";
|
||||
|
||||
export default class PopupAvatar extends PopupElement {
|
||||
private cropContainer: HTMLElement;
|
||||
@ -24,7 +25,7 @@ export default class PopupAvatar extends PopupElement {
|
||||
super('popup-avatar', null, {closable: true});
|
||||
|
||||
this.h6 = document.createElement('h6');
|
||||
this.h6.innerText = 'Drag to Reposition';
|
||||
_i18n(this.h6, 'Popup.Avatar.Title');
|
||||
|
||||
this.btnClose.classList.remove('btn-icon');
|
||||
|
||||
|
@ -1,19 +0,0 @@
|
||||
import PopupElement, { addCancelButton, PopupButton, PopupOptions } from ".";
|
||||
import { LangPackKey, _i18n } from "../../lib/langPack";
|
||||
|
||||
export default class PopupConfirmAction extends PopupElement {
|
||||
constructor(className: string, buttons: PopupButton[], options: PopupOptions & {title: LangPackKey, text: LangPackKey}) {
|
||||
super('popup-peer popup-confirm-action ' + className, addCancelButton(buttons), {
|
||||
overlayClosable: true,
|
||||
...options
|
||||
});
|
||||
|
||||
_i18n(this.title, options.title);
|
||||
|
||||
const p = document.createElement('p');
|
||||
p.classList.add('popup-description');
|
||||
_i18n(p, options.text);
|
||||
|
||||
this.container.insertBefore(p, this.header.nextElementSibling);
|
||||
}
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
import PopupElement, { PopupOptions } from ".";
|
||||
import { getFullDate } from "../../helpers/date";
|
||||
import mediaSizes from "../../helpers/mediaSizes";
|
||||
import I18n from "../../lib/langPack";
|
||||
import I18n, { i18n, LangPackKey } from "../../lib/langPack";
|
||||
import InputField from "../inputField";
|
||||
|
||||
export default class PopupDatePicker extends PopupElement {
|
||||
@ -237,23 +237,47 @@ export default class PopupDatePicker extends PopupElement {
|
||||
|
||||
public setTimeTitle() {
|
||||
if(this.btnConfirm && this.selectedDate) {
|
||||
let dayStr = '';
|
||||
let key: LangPackKey, args: any[] = [];
|
||||
const date = new Date();
|
||||
date.setHours(0, 0, 0, 0);
|
||||
|
||||
const timeOptions: Intl.DateTimeFormatOptions = {
|
||||
minute: '2-digit',
|
||||
hour: '2-digit',
|
||||
hour12: false
|
||||
};
|
||||
|
||||
const sendDate = new Date(this.selectedDate.getTime());
|
||||
sendDate.setHours(+this.hoursInputField.value, +this.minutesInputField.value);
|
||||
|
||||
if(this.selectedDate.getTime() === date.getTime()) {
|
||||
dayStr = 'Today';
|
||||
} else if(this.selectedDate.getTime() === (date.getTime() + 86400e3)) {
|
||||
key = 'Schedule.SendToday';
|
||||
}/* else if(this.selectedDate.getTime() === (date.getTime() + 86400e3)) {
|
||||
dayStr = 'Tomorrow';
|
||||
} else {
|
||||
dayStr = 'on ' + getFullDate(this.selectedDate, {
|
||||
noTime: true,
|
||||
monthAsNumber: true,
|
||||
leadingZero: true
|
||||
});
|
||||
} */ else {
|
||||
key = 'Schedule.SendDate';
|
||||
|
||||
const dateOptions: Intl.DateTimeFormatOptions = {
|
||||
month: 'short',
|
||||
day: 'numeric'
|
||||
};
|
||||
|
||||
if(sendDate.getFullYear() !== date.getFullYear()) {
|
||||
dateOptions.year = 'numeric';
|
||||
}
|
||||
|
||||
args.push(new I18n.IntlDateElement({
|
||||
date: sendDate,
|
||||
options: dateOptions
|
||||
}).element);
|
||||
}
|
||||
|
||||
this.btnConfirm.firstChild.nodeValue = 'Send ' + dayStr + ' at ' + ('00' + this.hoursInputField.value).slice(-2) + ':' + ('00' + this.minutesInputField.value).slice(-2);
|
||||
args.push(new I18n.IntlDateElement({
|
||||
date: sendDate,
|
||||
options: timeOptions
|
||||
}).element);
|
||||
|
||||
this.btnConfirm.firstChild.replaceWith(i18n(key, args));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,28 +1,51 @@
|
||||
import { PopupButton } from ".";
|
||||
import appChatsManager from "../../lib/appManagers/appChatsManager";
|
||||
import appMessagesManager from "../../lib/appManagers/appMessagesManager";
|
||||
import appPeersManager, { PeerType } from "../../lib/appManagers/appPeersManager";
|
||||
import PopupPeer from "./peer";
|
||||
import { LangPackKey } from "../../lib/langPack";
|
||||
import PeerTitle from "../peerTitle";
|
||||
import PopupPeer, { PopupPeerButtonCallbackCheckboxes, PopupPeerOptions } from "./peer";
|
||||
|
||||
export default class PopupDeleteDialog {
|
||||
constructor(peerId: number, peerType: PeerType = appPeersManager.getDialogType(peerId)) {
|
||||
let firstName = appPeersManager.getPeerTitle(peerId, false, true);
|
||||
constructor(peerId: number, peerType: PeerType = appPeersManager.getDialogType(peerId), onSelect?: (promise: Promise<any>) => void) {
|
||||
const peerTitleElement = new PeerTitle({
|
||||
peerId,
|
||||
onlyFirstName: true
|
||||
}).element;
|
||||
|
||||
let callbackFlush = (justClear?: true) => {
|
||||
appMessagesManager.flushHistory(peerId, justClear);
|
||||
/* const callbackFlush = (checked: PopupPeerButtonCallbackCheckboxes) => {
|
||||
const promise = appMessagesManager.flushHistory(peerId, checkboxes ? !checked[checkboxes[0].text] : undefined);
|
||||
onSelect && onSelect(promise);
|
||||
}; */
|
||||
|
||||
const callbackLeave = (checked: PopupPeerButtonCallbackCheckboxes) => {
|
||||
const promise = appChatsManager.leave(-peerId);
|
||||
onSelect && onSelect(promise);
|
||||
};
|
||||
|
||||
let callbackLeave = () => {
|
||||
appChatsManager.leave(-peerId);
|
||||
const callbackDelete = (checked: PopupPeerButtonCallbackCheckboxes) => {
|
||||
let promise: Promise<any>;
|
||||
|
||||
if(peerId > 0) {
|
||||
promise = appMessagesManager.flushHistory(peerId, false, checkboxes ? checked[checkboxes[0].text] : undefined);
|
||||
} else {
|
||||
if(checked[checkboxes[0].text]) {
|
||||
promise = appChatsManager.delete(-peerId);
|
||||
} else {
|
||||
promise = appChatsManager.leave(-peerId);
|
||||
}
|
||||
}
|
||||
|
||||
onSelect && onSelect(promise);
|
||||
};
|
||||
|
||||
let title: string, description: string, buttons: PopupButton[];
|
||||
let title: LangPackKey, description: LangPackKey, descriptionArgs: any[], buttons: PopupPeerOptions['buttons'], checkboxes: PopupPeerOptions['checkboxes'];
|
||||
switch(peerType) {
|
||||
case 'channel': {
|
||||
title = 'Leave Channel?';
|
||||
description = `Are you sure you want to leave this channel?`;
|
||||
title = 'LeaveChannelMenu';
|
||||
description = 'ChannelLeaveAlertWithName';
|
||||
descriptionArgs = [peerTitleElement];
|
||||
buttons = [{
|
||||
text: 'LEAVE ' + firstName,
|
||||
langKey: 'LeaveChannel',
|
||||
isDanger: true,
|
||||
callback: callbackLeave
|
||||
}];
|
||||
@ -30,71 +53,90 @@ export default class PopupDeleteDialog {
|
||||
break;
|
||||
}
|
||||
|
||||
case 'megagroup': {
|
||||
/* case 'megagroup': {
|
||||
title = 'Leave Group?';
|
||||
description = `Are you sure you want to leave this group?`;
|
||||
buttons = [{
|
||||
text: 'LEAVE ' + firstName,
|
||||
text: 'LEAVE ' + peerTitleElement,
|
||||
isDanger: true,
|
||||
callback: callbackLeave
|
||||
}];
|
||||
|
||||
break;
|
||||
}
|
||||
} */
|
||||
|
||||
case 'chat': {
|
||||
title = 'Delete Chat?';
|
||||
description = `Are you sure you want to delete chat with <b>${firstName}</b>?`;
|
||||
title = 'DeleteChatUser';
|
||||
description = 'AreYouSureDeleteThisChatWithUser';
|
||||
descriptionArgs = [peerTitleElement];
|
||||
|
||||
checkboxes = [{
|
||||
text: 'DeleteMessagesOptionAlso',
|
||||
textArgs: [
|
||||
new PeerTitle({
|
||||
peerId,
|
||||
onlyFirstName: true
|
||||
}).element
|
||||
]
|
||||
}];
|
||||
|
||||
buttons = [{
|
||||
text: 'DELETE FOR ME AND ' + firstName,
|
||||
langKey: 'DeleteChatUser',
|
||||
isDanger: true,
|
||||
callback: () => callbackFlush()
|
||||
}, {
|
||||
text: 'DELETE JUST FOR ME',
|
||||
isDanger: true,
|
||||
callback: () => callbackFlush(true)
|
||||
callback: callbackDelete
|
||||
}];
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case 'saved': {
|
||||
title = 'Delete Saved Messages?';
|
||||
description = `Are you sure you want to delete all your saved messages?`;
|
||||
title = 'DeleteChatUser';
|
||||
description = 'AreYouSureDeleteThisChatSavedMessages';
|
||||
buttons = [{
|
||||
text: 'DELETE SAVED MESSAGES',
|
||||
langKey: 'DeleteChatUser',
|
||||
isDanger: true,
|
||||
callback: () => callbackFlush()
|
||||
callback: callbackDelete
|
||||
}];
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case 'megagroup':
|
||||
case 'group': {
|
||||
title = 'Delete and leave Group?';
|
||||
description = `Are you sure you want to delete all message history and leave <b>${firstName}</b>?`;
|
||||
buttons = [{
|
||||
text: 'DELETE AND LEAVE ' + firstName,
|
||||
isDanger: true,
|
||||
callback: () => callbackLeave()
|
||||
}];
|
||||
if(appChatsManager.hasRights(-peerId, 'delete_chat')) {
|
||||
title = 'DeleteMegaMenu';
|
||||
description = 'AreYouSureDeleteAndExit';
|
||||
buttons = [{
|
||||
langKey: 'DeleteMegaMenu',
|
||||
isDanger: true,
|
||||
callback: callbackDelete
|
||||
}];
|
||||
|
||||
checkboxes = [{
|
||||
text: 'DeleteChat.DeleteGroupForAll'
|
||||
}];
|
||||
} else {
|
||||
title = 'LeaveMegaMenu';
|
||||
description = 'AreYouSureDeleteAndExitName';
|
||||
descriptionArgs = [peerTitleElement];
|
||||
buttons = [{
|
||||
langKey: 'DeleteChatUser',
|
||||
isDanger: true,
|
||||
callback: callbackLeave
|
||||
}];
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
buttons.push({
|
||||
text: 'CANCEL',
|
||||
isCancel: true
|
||||
});
|
||||
|
||||
let popup = new PopupPeer('popup-delete-chat', {
|
||||
new PopupPeer('popup-delete-chat', {
|
||||
peerId,
|
||||
title,
|
||||
description,
|
||||
buttons
|
||||
});
|
||||
|
||||
popup.show();
|
||||
titleLangKey: title,
|
||||
descriptionLangKey: description,
|
||||
descriptionLangArgs: descriptionArgs,
|
||||
buttons,
|
||||
checkboxes
|
||||
}).show();
|
||||
}
|
||||
}
|
@ -1,14 +1,18 @@
|
||||
import appChatsManager from "../../lib/appManagers/appChatsManager";
|
||||
import appMessagesManager from "../../lib/appManagers/appMessagesManager";
|
||||
import appPeersManager from "../../lib/appManagers/appPeersManager";
|
||||
import rootScope from "../../lib/rootScope";
|
||||
import { PopupButton } from ".";
|
||||
import { addCancelButton, PopupButton } from ".";
|
||||
import PopupPeer from "./peer";
|
||||
import { ChatType } from "../chat/chat";
|
||||
import { i18n, LangPackKey } from "../../lib/langPack";
|
||||
import PeerTitle from "../peerTitle";
|
||||
|
||||
export default class PopupDeleteMessages {
|
||||
constructor(peerId: number, mids: number[], type: ChatType, onConfirm?: () => void) {
|
||||
const firstName = appPeersManager.getPeerTitle(peerId, false, true);
|
||||
const peerTitleElement = new PeerTitle({
|
||||
peerId,
|
||||
onlyFirstName: true
|
||||
}).element;
|
||||
|
||||
mids = mids.slice();
|
||||
const callback = (revoke?: true) => {
|
||||
@ -20,26 +24,29 @@ export default class PopupDeleteMessages {
|
||||
}
|
||||
};
|
||||
|
||||
let title: string, description: string, buttons: PopupButton[];
|
||||
title = `Delete ${mids.length === 1 ? '' : mids.length + ' '}Message${mids.length === 1 ? '' : 's'}?`;
|
||||
description = `Are you sure you want to delete ${mids.length === 1 ? 'this message' : 'these messages'}?`;
|
||||
let title: LangPackKey, titleArgs: any[], description: LangPackKey, descriptionArgs: any[], buttons: PopupButton[];
|
||||
if(mids.length === 1) {
|
||||
title = 'DeleteSingleMessagesTitle';
|
||||
} else {
|
||||
title = 'DeleteMessagesTitle';
|
||||
titleArgs = [i18n('messages', [mids.length])];
|
||||
}
|
||||
|
||||
description = mids.length === 1 ? 'AreYouSureDeleteSingleMessage' : 'AreYouSureDeleteFewMessages';
|
||||
|
||||
buttons = [{
|
||||
langKey: 'Delete',
|
||||
isDanger: true,
|
||||
callback: () => callback()
|
||||
}];
|
||||
|
||||
if(peerId === rootScope.myId || type === 'scheduled') {
|
||||
buttons = [{
|
||||
text: 'DELETE',
|
||||
isDanger: true,
|
||||
callback: () => callback()
|
||||
}];
|
||||
} else {
|
||||
buttons = [{
|
||||
text: 'DELETE JUST FOR ME',
|
||||
isDanger: true,
|
||||
callback: () => callback()
|
||||
}];
|
||||
|
||||
} else {
|
||||
if(peerId > 0) {
|
||||
buttons.push({
|
||||
text: 'DELETE FOR ME AND ' + firstName,
|
||||
langKey: 'DeleteMessagesOptionAlso',
|
||||
langArgs: [peerTitleElement],
|
||||
isDanger: true,
|
||||
callback: () => callback(true)
|
||||
});
|
||||
@ -56,44 +63,36 @@ export default class PopupDeleteMessages {
|
||||
if(canRevoke.length) {
|
||||
if(canRevoke.length === mids.length) {
|
||||
buttons.push({
|
||||
text: 'DELETE FOR ALL',
|
||||
langKey: 'DeleteForAll',
|
||||
isDanger: true,
|
||||
callback: () => callback(true)
|
||||
});
|
||||
} else {
|
||||
const buttonText = 'Unsend my and delete';
|
||||
buttons.push({
|
||||
text: buttonText,
|
||||
langKey: 'DeleteMessagesOption',
|
||||
isDanger: true,
|
||||
callback: () => callback(true)
|
||||
});
|
||||
|
||||
description = `You can also delete the ${canRevoke.length} message${canRevoke.length > 1 ? 's' : ''} you sent from the inboxes of other group members by pressing "${buttonText}".`;
|
||||
description = 'DeleteMessagesTextGroup';
|
||||
descriptionArgs = [i18n('messages', [canRevoke.length])];
|
||||
//description = `You can also delete the ${canRevoke.length} message${canRevoke.length > 1 ? 's' : ''} you sent from the inboxes of other group members by pressing "${buttonText}".`;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
//if(!hasRights || appChatsManager.isBroadcast(-peerId) || appChatsManager.isMegagroup(-peerId)) {
|
||||
buttons.shift();
|
||||
//}
|
||||
|
||||
buttons.push({
|
||||
text: 'DELETE FOR ALL',
|
||||
isDanger: true,
|
||||
callback: () => callback(true)
|
||||
});
|
||||
buttons[0].callback = () => callback(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
buttons.push({
|
||||
text: 'CANCEL',
|
||||
isCancel: true
|
||||
});
|
||||
addCancelButton(buttons);
|
||||
|
||||
const popup = new PopupPeer('popup-delete-chat', {
|
||||
peerId,
|
||||
title,
|
||||
description,
|
||||
titleLangKey: title,
|
||||
titleLangArgs: titleArgs,
|
||||
descriptionLangKey: description,
|
||||
descriptionLangArgs: descriptionArgs,
|
||||
buttons
|
||||
});
|
||||
|
||||
|
@ -93,14 +93,14 @@ export default class PopupElement {
|
||||
const button = document.createElement('button');
|
||||
button.className = 'btn' + (b.isDanger ? ' danger' : ' primary');
|
||||
|
||||
ripple(button);
|
||||
|
||||
if(b.text) {
|
||||
button.innerHTML = b.text;
|
||||
} else {
|
||||
button.append(i18n(b.langKey, b.langArgs));
|
||||
}
|
||||
|
||||
ripple(button);
|
||||
|
||||
if(b.callback) {
|
||||
button.addEventListener('click', () => {
|
||||
b.callback();
|
||||
|
@ -1,32 +1,67 @@
|
||||
import AvatarElement from "../avatar";
|
||||
import PopupElement, { PopupButton } from ".";
|
||||
import PopupElement, { addCancelButton, PopupButton, PopupOptions } from ".";
|
||||
import { i18n, LangPackKey } from "../../lib/langPack";
|
||||
import CheckboxField, { CheckboxFieldOptions } from "../checkboxField";
|
||||
|
||||
export type PopupPeerButtonCallbackCheckboxes = {[text in LangPackKey]: boolean};
|
||||
export type PopupPeerButtonCallback = (checkboxes?: PopupPeerButtonCallbackCheckboxes) => void;
|
||||
|
||||
export type PopupPeerOptions = PopupOptions & Partial<{
|
||||
peerId: number,
|
||||
title: string,
|
||||
titleLangKey?: LangPackKey,
|
||||
titleLangArgs?: any[],
|
||||
description: string,
|
||||
descriptionLangKey?: LangPackKey,
|
||||
descriptionLangArgs?: any[],
|
||||
buttons: Array<Omit<PopupButton, 'callback'> & Partial<{callback: PopupPeerButtonCallback}>>,
|
||||
checkboxes: Array<CheckboxFieldOptions & {checkboxField?: CheckboxField}>
|
||||
}>;
|
||||
export default class PopupPeer extends PopupElement {
|
||||
constructor(private className: string, options: Partial<{
|
||||
peerId: number,
|
||||
title: string,
|
||||
titleLangKey?: LangPackKey,
|
||||
description: string,
|
||||
descriptionLangKey?: LangPackKey,
|
||||
buttons: Array<PopupButton>
|
||||
}> = {}) {
|
||||
super('popup-peer' + (className ? ' ' + className : ''), options.buttons, {overlayClosable: true});
|
||||
constructor(private className: string, options: PopupPeerOptions = {}) {
|
||||
super('popup-peer' + (className ? ' ' + className : ''), addCancelButton(options.buttons), {overlayClosable: true, ...options});
|
||||
|
||||
let avatarEl = new AvatarElement();
|
||||
avatarEl.setAttribute('dialog', '1');
|
||||
avatarEl.setAttribute('peer', '' + options.peerId);
|
||||
avatarEl.classList.add('avatar-32');
|
||||
if(options.peerId) {
|
||||
let avatarEl = new AvatarElement();
|
||||
avatarEl.setAttribute('dialog', '1');
|
||||
avatarEl.setAttribute('peer', '' + options.peerId);
|
||||
avatarEl.classList.add('avatar-32');
|
||||
this.header.prepend(avatarEl);
|
||||
}
|
||||
|
||||
if(options.descriptionLangKey) this.title.append(i18n(options.titleLangKey));
|
||||
if(options.descriptionLangKey) this.title.append(i18n(options.titleLangKey, options.titleLangArgs));
|
||||
else this.title.innerText = options.title || '';
|
||||
this.header.prepend(avatarEl);
|
||||
|
||||
let p = document.createElement('p');
|
||||
p.classList.add('popup-description');
|
||||
if(options.descriptionLangKey) p.append(i18n(options.descriptionLangKey));
|
||||
if(options.descriptionLangKey) p.append(i18n(options.descriptionLangKey, options.descriptionLangArgs));
|
||||
else p.innerHTML = options.description;
|
||||
|
||||
this.container.insertBefore(p, this.header.nextElementSibling);
|
||||
const fragment = document.createDocumentFragment();
|
||||
fragment.append(p);
|
||||
|
||||
if(options.checkboxes) {
|
||||
options.checkboxes.forEach(o => {
|
||||
o.withRipple = true;
|
||||
const checkboxField = new CheckboxField(o);
|
||||
o.checkboxField = checkboxField;
|
||||
fragment.append(checkboxField.label);
|
||||
});
|
||||
|
||||
options.buttons.forEach(button => {
|
||||
if(button.callback) {
|
||||
const original = button.callback;
|
||||
button.callback = () => {
|
||||
const c: PopupPeerButtonCallbackCheckboxes = {};
|
||||
options.checkboxes.forEach(o => {
|
||||
c[o.text] = o.checkboxField.checked;
|
||||
});
|
||||
original(c);
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
this.container.insertBefore(fragment, this.header.nextElementSibling);
|
||||
}
|
||||
}
|
||||
|
@ -1,12 +1,15 @@
|
||||
import appMessagesManager from "../../lib/appManagers/appMessagesManager";
|
||||
import { PopupButton } from ".";
|
||||
import { addCancelButton, PopupButton } from ".";
|
||||
import PopupPeer from "./peer";
|
||||
import appPeersManager from "../../lib/appManagers/appPeersManager";
|
||||
import rootScope from "../../lib/rootScope";
|
||||
import { LangPackKey } from "../../lib/langPack";
|
||||
import appChatsManager from "../../lib/appManagers/appChatsManager";
|
||||
import PeerTitle from "../peerTitle";
|
||||
|
||||
export default class PopupPinMessage {
|
||||
constructor(peerId: number, mid: number, unpin?: true, onConfirm?: () => void) {
|
||||
let title: string, description: string, buttons: PopupButton[] = [];
|
||||
let title: LangPackKey, description: string, buttons: PopupButton[] = [];
|
||||
|
||||
const canUnpin = appPeersManager.canPinMessage(peerId);
|
||||
|
||||
@ -29,74 +32,76 @@ export default class PopupPinMessage {
|
||||
}, 300);
|
||||
};
|
||||
|
||||
const firstName = appPeersManager.getPeerTitle(peerId, false, true);
|
||||
|
||||
if(unpin) {
|
||||
let buttonText = 'UNPIN';
|
||||
let buttonText: LangPackKey = 'UnpinMessage';
|
||||
if(!mid) {
|
||||
if(canUnpin) {
|
||||
title = 'Unpin All Messages?';
|
||||
description = 'Would you like to unpin all messages?';
|
||||
title = 'Popup.Unpin.AllTitle';
|
||||
description = 'Chat.UnpinAllMessagesConfirmation';
|
||||
} else {
|
||||
title = 'Hide Pinned Messages?';
|
||||
description = 'Do you want to hide the pinned message bar? It wil stay hidden until a new message is pinned.';
|
||||
buttonText = 'HIDE';
|
||||
title = 'Popup.Unpin.HideTitle';
|
||||
description = 'Popup.Unpin.HideDescription';
|
||||
buttonText = 'Popup.Unpin.Hide';
|
||||
}
|
||||
} else {
|
||||
title = `Unpin Message?`;
|
||||
description = 'Would you like to unpin this message?';
|
||||
title = 'UnpinMessageAlertTitle';
|
||||
description = 'Chat.Confirm.Unpin';
|
||||
}
|
||||
|
||||
buttons.push({
|
||||
text: buttonText,
|
||||
langKey: buttonText,
|
||||
isDanger: true,
|
||||
callback: () => callback()
|
||||
});
|
||||
} else {
|
||||
title = 'Pin Message?';
|
||||
title = 'PinMessageAlertTitle';
|
||||
const pinButtonText: LangPackKey = 'PinMessage';
|
||||
|
||||
if(peerId < 0) {
|
||||
description = 'Do you want to pin this message for all members in the group?';
|
||||
buttons.push({
|
||||
text: 'PIN AND NOTIFY',
|
||||
langKey: pinButtonText,
|
||||
callback: () => callback()
|
||||
});
|
||||
|
||||
buttons.push({
|
||||
text: 'PIN WITHOUT NOTIFYING',
|
||||
callback: () => callback(undefined, true)
|
||||
});
|
||||
if(appChatsManager.isBroadcast(-peerId)) {
|
||||
description = 'PinMessageAlertChannel';
|
||||
} else {
|
||||
description = 'PinMessageAlert';
|
||||
|
||||
buttons.push({
|
||||
langKey: 'PinNotify',
|
||||
callback: () => callback(undefined, true)
|
||||
});
|
||||
}
|
||||
} else {
|
||||
description = 'Would you like to pin this message?';
|
||||
description = 'PinMessageAlertChat';
|
||||
|
||||
if(peerId === rootScope.myId) {
|
||||
buttons.push({
|
||||
text: 'PIN',
|
||||
langKey: pinButtonText,
|
||||
callback: () => callback()
|
||||
});
|
||||
} else {
|
||||
buttons.push({
|
||||
text: 'PIN JUST FOR ME',
|
||||
langKey: pinButtonText,
|
||||
callback: () => callback(true)
|
||||
});
|
||||
|
||||
buttons.push({
|
||||
text: 'PIN FOR ME AND ' + firstName,
|
||||
langKey: 'PinAlsoFor',
|
||||
langArgs: [new PeerTitle({peerId, onlyFirstName: true}).element],
|
||||
callback: () => callback()
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
buttons.push({
|
||||
text: 'CANCEL',
|
||||
isCancel: true
|
||||
});
|
||||
addCancelButton(buttons);
|
||||
|
||||
const popup = new PopupPeer('popup-delete-chat', {
|
||||
peerId,
|
||||
title,
|
||||
description,
|
||||
titleLangKey: title,
|
||||
descriptionLangKey: description,
|
||||
buttons
|
||||
});
|
||||
|
||||
|
@ -6,12 +6,12 @@ import { SliderSuperTab } from "../../../slider";
|
||||
import { wrapSticker } from "../../../wrappers";
|
||||
import InputField from "../../../inputField";
|
||||
import { attachClickEvent, cancelEvent, canFocus } from "../../../../helpers/dom";
|
||||
import PopupConfirmAction from "../../../popups/confirmAction";
|
||||
import { putPreloader } from "../../../misc";
|
||||
import passwordManager from "../../../../lib/mtproto/passwordManager";
|
||||
import AppTwoStepVerificationSetTab from "./passwordSet";
|
||||
import AppTwoStepVerificationEmailConfirmationTab from "./emailConfirmation";
|
||||
import RichTextProcessor from "../../../../lib/richtextprocessor";
|
||||
import PopupPeer from "../../../popups/peer";
|
||||
|
||||
export default class AppTwoStepVerificationEmailTab extends SliderSuperTab {
|
||||
public inputField: InputField;
|
||||
@ -129,30 +129,31 @@ export default class AppTwoStepVerificationEmailTab extends SliderSuperTab {
|
||||
};
|
||||
|
||||
attachClickEvent(btnSkip, (e) => {
|
||||
const popup = new PopupConfirmAction('popup-skip-email', [{
|
||||
text: 'CANCEL',
|
||||
isCancel: true
|
||||
}, {
|
||||
text: 'SKIP',
|
||||
callback: () => {
|
||||
//inputContent.classList.add('sidebar-left-section-disabled');
|
||||
toggleButtons(true);
|
||||
putPreloader(btnSkip);
|
||||
passwordManager.updateSettings({
|
||||
hint: this.hint,
|
||||
currentPassword: this.plainPassword,
|
||||
newPassword: this.newPassword,
|
||||
email: ''
|
||||
}).then(() => {
|
||||
goNext();
|
||||
}, (err) => {
|
||||
toggleButtons(false);
|
||||
});
|
||||
},
|
||||
isDanger: true,
|
||||
}], {
|
||||
title: 'Warning',
|
||||
text: 'No, seriously.<br/><br/>If you forget your password, you will lose access to your Telegram account. There will be no way to restore it.'
|
||||
const popup = new PopupPeer('popup-skip-email', {
|
||||
buttons: [{
|
||||
text: 'CANCEL',
|
||||
isCancel: true
|
||||
}, {
|
||||
text: 'SKIP',
|
||||
callback: () => {
|
||||
//inputContent.classList.add('sidebar-left-section-disabled');
|
||||
toggleButtons(true);
|
||||
putPreloader(btnSkip);
|
||||
passwordManager.updateSettings({
|
||||
hint: this.hint,
|
||||
currentPassword: this.plainPassword,
|
||||
newPassword: this.newPassword,
|
||||
email: ''
|
||||
}).then(() => {
|
||||
goNext();
|
||||
}, (err) => {
|
||||
toggleButtons(false);
|
||||
});
|
||||
},
|
||||
isDanger: true,
|
||||
}],
|
||||
titleLangKey: 'Warning',
|
||||
descriptionLangKey: 'No, seriously.<br/><br/>If you forget your password, you will lose access to your Telegram account. There will be no way to restore it.'
|
||||
});
|
||||
|
||||
popup.show();
|
||||
|
@ -4,7 +4,7 @@ import { AccountPassword } from "../../../../layer";
|
||||
import appStickersManager from "../../../../lib/appManagers/appStickersManager";
|
||||
import passwordManager from "../../../../lib/mtproto/passwordManager";
|
||||
import Button from "../../../button";
|
||||
import PopupConfirmAction from "../../../popups/confirmAction";
|
||||
import PopupPeer from "../../../popups/peer";
|
||||
import { SliderSuperTab } from "../../../slider";
|
||||
import { wrapSticker } from "../../../wrappers";
|
||||
import AppSettingsTab from "../settings";
|
||||
@ -62,18 +62,19 @@ export default class AppTwoStepVerificationTab extends SliderSuperTab {
|
||||
});
|
||||
|
||||
attachClickEvent(btnDisablePassword, () => {
|
||||
const popup = new PopupConfirmAction('popup-disable-password', [{
|
||||
text: 'DISABLE',
|
||||
callback: () => {
|
||||
passwordManager.updateSettings({currentPassword: this.plainPassword}).then(() => {
|
||||
this.slider.sliceTabsUntilTab(AppSettingsTab, this);
|
||||
this.close();
|
||||
});
|
||||
},
|
||||
isDanger: true,
|
||||
}], {
|
||||
title: 'Warning',
|
||||
text: 'Are you sure you want to disable<br/>your password?'
|
||||
const popup = new PopupPeer('popup-disable-password', {
|
||||
buttons: [{
|
||||
text: 'DISABLE',
|
||||
callback: () => {
|
||||
passwordManager.updateSettings({currentPassword: this.plainPassword}).then(() => {
|
||||
this.slider.sliceTabsUntilTab(AppSettingsTab, this);
|
||||
this.close();
|
||||
});
|
||||
},
|
||||
isDanger: true,
|
||||
}],
|
||||
titleLangKey: 'Warning',
|
||||
descriptionLangKey: 'Are you sure you want to disable<br/>your password?'
|
||||
});
|
||||
|
||||
popup.show();
|
||||
|
@ -7,11 +7,11 @@ import { formatDateAccordingToToday } from "../../../helpers/date";
|
||||
import { attachContextMenuListener, openBtnMenu, positionMenu } from "../../misc";
|
||||
import { attachClickEvent, findUpClassName, toggleDisability } from "../../../helpers/dom";
|
||||
import ButtonMenu from "../../buttonMenu";
|
||||
import PopupConfirmAction from "../../popups/confirmAction";
|
||||
import apiManager from "../../../lib/mtproto/mtprotoworker";
|
||||
import { toast } from "../../toast";
|
||||
import AppPrivacyAndSecurityTab from "./privacyAndSecurity";
|
||||
import I18n from "../../../lib/langPack";
|
||||
import PopupPeer from "../../popups/peer";
|
||||
|
||||
export default class AppActiveSessionsTab extends SliderSuperTab {
|
||||
public privacyTab: AppPrivacyAndSecurityTab;
|
||||
@ -56,23 +56,24 @@ export default class AppActiveSessionsTab extends SliderSuperTab {
|
||||
if(authorizations.length) {
|
||||
const btnTerminate = Button('btn-primary btn-transparent danger', {icon: 'stop', text: 'TerminateAllSessions'});
|
||||
attachClickEvent(btnTerminate, (e) => {
|
||||
new PopupConfirmAction('revoke-session', [{
|
||||
langKey: 'Terminate',
|
||||
isDanger: true,
|
||||
callback: () => {
|
||||
const toggle = toggleDisability([btnTerminate], true);
|
||||
apiManager.invokeApi('auth.resetAuthorizations').then(value => {
|
||||
//toggleDisability([btnTerminate], false);
|
||||
btnTerminate.remove();
|
||||
otherSection.container.remove();
|
||||
this.privacyTab.updateActiveSessions();
|
||||
}, onError).finally(() => {
|
||||
toggle();
|
||||
});
|
||||
}
|
||||
}], {
|
||||
title: 'AreYouSureSessionsTitle',
|
||||
text: 'AreYouSureSessions'
|
||||
new PopupPeer('revoke-session', {
|
||||
buttons: [{
|
||||
langKey: 'Terminate',
|
||||
isDanger: true,
|
||||
callback: () => {
|
||||
const toggle = toggleDisability([btnTerminate], true);
|
||||
apiManager.invokeApi('auth.resetAuthorizations').then(value => {
|
||||
//toggleDisability([btnTerminate], false);
|
||||
btnTerminate.remove();
|
||||
otherSection.container.remove();
|
||||
this.privacyTab.updateActiveSessions();
|
||||
}, onError).finally(() => {
|
||||
toggle();
|
||||
});
|
||||
}
|
||||
}],
|
||||
titleLangKey: 'AreYouSureSessionsTitle',
|
||||
descriptionLangKey: 'AreYouSureSessions'
|
||||
}).show();
|
||||
});
|
||||
|
||||
@ -106,21 +107,22 @@ export default class AppActiveSessionsTab extends SliderSuperTab {
|
||||
const onTerminateClick = () => {
|
||||
const hash = target.dataset.hash;
|
||||
|
||||
new PopupConfirmAction('revoke-session', [{
|
||||
langKey: 'Terminate',
|
||||
isDanger: true,
|
||||
callback: () => {
|
||||
apiManager.invokeApi('account.resetAuthorization', {hash})
|
||||
.then(value => {
|
||||
if(value) {
|
||||
target.remove();
|
||||
this.privacyTab.updateActiveSessions();
|
||||
}
|
||||
}, onError);
|
||||
}
|
||||
}], {
|
||||
title: 'AreYouSureSessionTitle',
|
||||
text: 'TerminateSessionText'
|
||||
new PopupPeer('revoke-session', {
|
||||
buttons: [{
|
||||
langKey: 'Terminate',
|
||||
isDanger: true,
|
||||
callback: () => {
|
||||
apiManager.invokeApi('account.resetAuthorization', {hash})
|
||||
.then(value => {
|
||||
if(value) {
|
||||
target.remove();
|
||||
this.privacyTab.updateActiveSessions();
|
||||
}
|
||||
}, onError);
|
||||
}
|
||||
}],
|
||||
titleLangKey: 'AreYouSureSessionTitle',
|
||||
descriptionLangKey: 'TerminateSessionText'
|
||||
}).show();
|
||||
};
|
||||
|
||||
|
@ -126,7 +126,7 @@ export default class AppEditContactTab extends SliderSuperTab {
|
||||
|
||||
});
|
||||
|
||||
const btnDelete = Button('btn-primary btn-transparent danger', {icon: 'delete', text: 'Delete Contact'});
|
||||
const btnDelete = Button('btn-primary btn-transparent danger', {icon: 'delete', text: 'PeerInfo.DeleteContact'});
|
||||
|
||||
attachClickEvent(btnDelete, () => {
|
||||
new PopupPeer('popup-delete-contact', {
|
||||
|
@ -15,6 +15,7 @@ import AppGroupTypeTab from "./groupType";
|
||||
import rootScope from "../../../lib/rootScope";
|
||||
import AppGroupPermissionsTab from "./groupPermissions";
|
||||
import { i18n } from "../../../lib/langPack";
|
||||
import PopupDeleteDialog from "../../popups/deleteDialog";
|
||||
|
||||
export default class AppEditGroupTab extends SliderSuperTab {
|
||||
private groupNameInputField: InputField;
|
||||
@ -196,42 +197,20 @@ export default class AppEditGroupTab extends SliderSuperTab {
|
||||
this.scrollable.append(section.container);
|
||||
}
|
||||
|
||||
if(appChatsManager.isChannel(this.chatId) && appChatsManager.hasRights(this.chatId, 'delete_chat')) {
|
||||
if(appChatsManager.hasRights(this.chatId, 'delete_chat')) {
|
||||
const section = new SettingSection({});
|
||||
|
||||
const btnDelete = Button('btn-primary btn-transparent danger', {icon: 'delete', text: 'DeleteMega'});
|
||||
|
||||
attachClickEvent(btnDelete, () => {
|
||||
new PopupPeer('popup-delete-group', {
|
||||
peerId: -this.chatId,
|
||||
titleLangKey: 'DeleteMegaMenu',
|
||||
descriptionLangKey: 'AreYouSureDeleteAndExit',
|
||||
buttons: addCancelButton([{
|
||||
langKey: 'DeleteMegaMenu',
|
||||
callback: () => {
|
||||
const toggle = toggleDisability([btnDelete], true);
|
||||
},
|
||||
isDanger: true
|
||||
}, {
|
||||
langKey: 'DeleteChat.DeleteGroupForAll',
|
||||
callback: () => {
|
||||
const toggle = toggleDisability([btnDelete], true);
|
||||
|
||||
appChatsManager.deleteChannel(this.chatId).then(() => {
|
||||
this.close();
|
||||
}, () => {
|
||||
toggle();
|
||||
});
|
||||
|
||||
/* appChatsManager.deleteChannel(this.chatId).then(() => {
|
||||
this.close();
|
||||
}, () => {
|
||||
toggle();
|
||||
}); */
|
||||
},
|
||||
isDanger: true
|
||||
}])
|
||||
}).show();
|
||||
new PopupDeleteDialog(-this.chatId, undefined, (promise) => {
|
||||
const toggle = toggleDisability([btnDelete], true);
|
||||
promise.then(() => {
|
||||
this.close();
|
||||
}, () => {
|
||||
toggle();
|
||||
});
|
||||
});
|
||||
}, {listenerSetter: this.listenerSetter});
|
||||
|
||||
section.content.append(btnDelete);
|
||||
|
@ -6,7 +6,6 @@ import appChatsManager from "../../../lib/appManagers/appChatsManager";
|
||||
import appProfileManager from "../../../lib/appManagers/appProfileManager";
|
||||
import Button from "../../button";
|
||||
import { setButtonLoader } from "../../misc";
|
||||
import PopupConfirmAction from "../../popups/confirmAction";
|
||||
import RadioField from "../../radioField";
|
||||
import Row, { RadioFormFromRows } from "../../row";
|
||||
import { SettingSection } from "../../sidebarLeft";
|
||||
@ -14,6 +13,7 @@ import { toast } from "../../toast";
|
||||
import { UsernameInputField } from "../../usernameInputField";
|
||||
import { SliderSuperTabEventable } from "../../sliderTab";
|
||||
import I18n from "../../../lib/langPack";
|
||||
import PopupPeer from "../../popups/peer";
|
||||
|
||||
export default class AppGroupTypeTab extends SliderSuperTabEventable {
|
||||
public peerId: number;
|
||||
@ -73,21 +73,22 @@ export default class AppGroupTypeTab extends SliderSuperTabEventable {
|
||||
const btnRevoke = Button('btn-primary btn-transparent danger', {icon: 'delete', text: 'RevokeLink'});
|
||||
|
||||
attachClickEvent(btnRevoke, () => {
|
||||
new PopupConfirmAction('revoke-link', [{
|
||||
langKey: 'RevokeButton',
|
||||
callback: () => {
|
||||
const toggle = toggleDisability([btnRevoke], true);
|
||||
new PopupPeer('revoke-link', {
|
||||
buttons: [{
|
||||
langKey: 'RevokeButton',
|
||||
callback: () => {
|
||||
const toggle = toggleDisability([btnRevoke], true);
|
||||
|
||||
appProfileManager.getChatInviteLink(-this.peerId, true).then(link => {
|
||||
toggle();
|
||||
linkRow.title.innerHTML = link;
|
||||
//revoked = true;
|
||||
//onChange();
|
||||
});
|
||||
}
|
||||
}], {
|
||||
title: 'RevokeLink',
|
||||
text: 'RevokeAlert'
|
||||
appProfileManager.getChatInviteLink(-this.peerId, true).then(link => {
|
||||
toggle();
|
||||
linkRow.title.innerHTML = link;
|
||||
//revoked = true;
|
||||
//onChange();
|
||||
});
|
||||
}
|
||||
}],
|
||||
titleLangKey: 'RevokeLink',
|
||||
descriptionLangKey: 'RevokeAlert'
|
||||
}).show();
|
||||
}, {listenerSetter: this.listenerSetter});
|
||||
|
||||
|
53
src/lang.ts
53
src/lang.ts
@ -81,6 +81,12 @@ const lang = {
|
||||
"one_value": "Remove %d Sticker",
|
||||
"other_value": "Remove %d Stickers"
|
||||
},
|
||||
"ForwardedFrom": "Forwarded from %s",
|
||||
"Popup.Avatar.Title": "Drag to Reposition",
|
||||
"Popup.Unpin.AllTitle": "Unpin all messages",
|
||||
"Popup.Unpin.HideTitle": "Hide pinned messages",
|
||||
"Popup.Unpin.HideDescription": "Do you want to hide the pinned message bar? It wil stay hidden until a new message is pinned.",
|
||||
"Popup.Unpin.Hide": "Hide",
|
||||
|
||||
// * android
|
||||
"ActionCreateChannel": "Channel created",
|
||||
@ -153,6 +159,10 @@ const lang = {
|
||||
"one_value": "%1$d member",
|
||||
"other_value": "%1$d members"
|
||||
},
|
||||
"messages": {
|
||||
"one_value": "%1$d message",
|
||||
"other_value": "%1$d messages"
|
||||
},
|
||||
"UsernameHelpLink": "This link opens a chat with you:\n%1$s",
|
||||
"NewChannel": "New Channel",
|
||||
"NewGroup": "New Group",
|
||||
@ -214,6 +224,13 @@ const lang = {
|
||||
"TerminateAllSessions": "Terminate All Other Sessions",
|
||||
"TerminateSessionText": "Are you sure you want to terminate this session?",
|
||||
"OtherSessions": "Active sessions",
|
||||
"AreYouSureDeleteSingleMessage": "Are you sure you want to delete this message?",
|
||||
"AreYouSureDeleteFewMessages": "Are you sure you want to delete these messages?",
|
||||
"AreYouSureDeleteSingleMessageMega": "Are you sure you want to delete this message for everyone?",
|
||||
"AreYouSureDeleteFewMessagesMega": "Are you sure you want to delete these messages for everyone?",
|
||||
"AreYouSureDeleteAndExitName": "Are you sure you want to delete and leave the group **%1$s**?",
|
||||
"AreYouSureDeleteThisChatSavedMessages": "Are you sure you want to delete **Saved Messages**?",
|
||||
"AreYouSureDeleteThisChatWithUser": "Are you sure you want to delete the chat with **%1$s**?",
|
||||
"AreYouSureSessionTitle": "Terminate session",
|
||||
"AreYouSureSessionsTitle": "Terminate sessions",
|
||||
"AreYouSureSessions": "Are you sure you want to terminate all other sessions?",
|
||||
@ -314,6 +331,27 @@ const lang = {
|
||||
"other_value": "%1$d subscribers"
|
||||
},
|
||||
"SearchGifsTitle": "Search GIFs",
|
||||
"PinMessageAlert": "Pin this message in the group?",
|
||||
"PinMessageAlertTitle": "Pin message",
|
||||
"PinMessageAlertChannel": "Do you want to pin this message in this channel?",
|
||||
"PinMessageAlertChat": "Do you want to pin this message at the top of the chat?",
|
||||
"UnpinMessageAlertTitle": "Unpin message",
|
||||
"UnpinMessageAlert": "Do you want to unpin this message?",
|
||||
"PinNotify": "Notify all members",
|
||||
"PinMessage": "Pin",
|
||||
"UnpinMessage": "Unpin",
|
||||
"PinAlsoFor": "Also pin for %1$s",
|
||||
"DeleteMessagesTitle": "Delete %1$s",
|
||||
"DeleteSingleMessagesTitle": "Delete message",
|
||||
"DeleteMessagesOption": "Unsend My Messages",
|
||||
"DeleteMessagesOptionAlso": "Also delete for %1$s",
|
||||
"DeleteForAll": "Delete for all members",
|
||||
"DeleteMessagesTextGroup": "You can also delete the %1$s you sent from the inboxes of other group members by checking \"Unsend my messages\".",
|
||||
"LeaveChannel": "Leave Channel",
|
||||
"LeaveChannelMenu": "Leave channel",
|
||||
"ChannelLeaveAlertWithName": "Are you sure you want to leave **%1$s**?",
|
||||
"LeaveMegaMenu": "Leave group",
|
||||
"DeleteChatUser": "Delete chat",
|
||||
|
||||
// * macos
|
||||
"AccountSettings.Filters": "Chat Folders",
|
||||
@ -324,6 +362,7 @@ const lang = {
|
||||
"Channel.UsernameAboutChannel": "People can share this link with others and can find your channel using Telegram search.",
|
||||
"Channel.UsernameAboutGroup": "People can share this link with others and find your group using Telegram search.",
|
||||
"Chat.CopySelectedText": "Copy Selected Text",
|
||||
"Chat.Confirm.Unpin": "Would you like to unpin this message?",
|
||||
"Chat.Date.ScheduledFor": "Scheduled for %@",
|
||||
//"Chat.Date.ScheduledUntilOnline": "Scheduled until online",
|
||||
"Chat.Date.ScheduledForToday": "Scheduled for today",
|
||||
@ -368,10 +407,18 @@ const lang = {
|
||||
"Chat.Send.WithoutSound": "Send Without Sound",
|
||||
"Chat.Send.SetReminder": "Set a Reminder",
|
||||
"Chat.Send.ScheduledMessage": "Schedule Message",
|
||||
"Chat.UnpinAllMessagesConfirmation": {
|
||||
"one_value": "Do you want to unpin %d message in this chat?",
|
||||
"other_value": "Do you want to unpin all %d messages in this chat?"
|
||||
},
|
||||
"ChatList.Context.Mute": "Mute",
|
||||
"ChatList.Context.Unmute": "Unmute",
|
||||
"ChatList.Context.Pin": "Pin",
|
||||
"ChatList.Context.Unpin": "Unpin",
|
||||
"ChatList.Context.DeleteChat": "Delete Chat",
|
||||
"ChatList.Context.DeleteAndExit": "Delete and Leave",
|
||||
"ChatList.Context.LeaveChannel": "Leave Channel",
|
||||
"ChatList.Context.LeaveGroup": "Leave Group",
|
||||
"ChatList.Service.Call.incoming": "Incoming Call (%@)",
|
||||
"ChatList.Service.Call.outgoing": "Outgoing Call (%@)",
|
||||
"ChatList.Service.Call.Cancelled": "Cancelled Call",
|
||||
@ -458,6 +505,7 @@ const lang = {
|
||||
"PeerInfo.SignMessages": "Sign Messages",
|
||||
"PeerInfo.SharedMedia": "Shared Media",
|
||||
"PeerInfo.Subscribers": "Subscribers",
|
||||
"PeerInfo.DeleteContact": "Delete Contact",
|
||||
"PollResults.Title.Poll": "Poll Results",
|
||||
"PollResults.Title.Quiz": "Quiz Results",
|
||||
"PollResults.LoadMore": {
|
||||
@ -512,7 +560,10 @@ const lang = {
|
||||
"NewPoll.OptionsAddOption": "Add an Option",
|
||||
"NewPoll.MultipleChoice": "Multiple Answers",
|
||||
"NewPoll.Quiz": "Quiz Mode",
|
||||
"GroupPermission.Delete": "Delete Exception"
|
||||
"GroupPermission.Delete": "Delete Exception",
|
||||
"Schedule.SendToday": "Send today at %@",
|
||||
"Schedule.SendDate": "Send on %@ at %@",
|
||||
//"Schedule.SendWhenOnline": "Send When Online"
|
||||
};
|
||||
|
||||
export default lang;
|
||||
|
1064
src/layer.d.ts
vendored
1064
src/layer.d.ts
vendored
File diff suppressed because it is too large
Load Diff
@ -62,16 +62,17 @@ export class AppChatsManager {
|
||||
|
||||
case 'updateUserTyping':
|
||||
case 'updateChatUserTyping': {
|
||||
if(rootScope.myId === update.user_id) {
|
||||
const fromId = (update as Update.updateUserTyping).user_id || appPeersManager.getPeerId((update as Update.updateChatUserTyping).from_id);
|
||||
if(rootScope.myId === fromId) {
|
||||
return;
|
||||
}
|
||||
|
||||
const peerId = update._ === 'updateUserTyping' ? update.user_id : -update.chat_id;
|
||||
const peerId = update._ === 'updateUserTyping' ? fromId : -update.chat_id;
|
||||
const typings = this.typingsInPeer[peerId] ?? (this.typingsInPeer[peerId] = []);
|
||||
let typing = typings.find(t => t.userId === update.user_id);
|
||||
let typing = typings.find(t => t.userId === fromId);
|
||||
if(!typing) {
|
||||
typing = {
|
||||
userId: update.user_id
|
||||
userId: fromId
|
||||
};
|
||||
|
||||
typings.push(typing);
|
||||
@ -81,7 +82,7 @@ export class AppChatsManager {
|
||||
|
||||
typing.action = update.action;
|
||||
|
||||
if(!appUsersManager.hasUser(update.user_id)) {
|
||||
if(!appUsersManager.hasUser(fromId)) {
|
||||
if(update._ === 'updateChatUserTyping') {
|
||||
if(update.chat_id && appChatsManager.hasChat(update.chat_id) && !appChatsManager.isChannel(update.chat_id)) {
|
||||
appProfileManager.getChatFull(update.chat_id);
|
||||
@ -91,13 +92,13 @@ export class AppChatsManager {
|
||||
//return;
|
||||
}
|
||||
|
||||
appUsersManager.forceUserOnline(update.user_id);
|
||||
appUsersManager.forceUserOnline(fromId);
|
||||
|
||||
if(typing.timeout !== undefined) clearTimeout(typing.timeout);
|
||||
|
||||
typing.timeout = window.setTimeout(() => {
|
||||
delete typing.timeout;
|
||||
typings.findAndSplice(t => t.userId === update.user_id);
|
||||
typings.findAndSplice(t => t.userId === fromId);
|
||||
|
||||
rootScope.broadcast('peer_typings', {peerId, typings});
|
||||
|
||||
@ -571,22 +572,36 @@ export class AppChatsManager {
|
||||
}).then(this.onChatUpdated.bind(this, id));
|
||||
}
|
||||
|
||||
public leaveChat(id: number) {
|
||||
return this.deleteChatUser(id, appUsersManager.getSelf().id).then(() => {
|
||||
public leaveChat(id: number, flushHistory = true) {
|
||||
let promise: Promise<any> = this.deleteChatUser(id, appUsersManager.getSelf().id)
|
||||
if(flushHistory) promise = promise.then(() => {
|
||||
return appMessagesManager.flushHistory(-id);
|
||||
});
|
||||
return promise;;
|
||||
}
|
||||
|
||||
public leave(id: number) {
|
||||
return this.isChannel(id) ? this.leaveChannel(id) : this.leaveChat(id);
|
||||
}
|
||||
|
||||
public delete(id: number) {
|
||||
return this.isChannel(id) ? this.deleteChannel(id) : this.deleteChat(id);
|
||||
}
|
||||
|
||||
public deleteChannel(id: number) {
|
||||
return apiManager.invokeApi('channels.deleteChannel', {
|
||||
channel: this.getChannelInput(id)
|
||||
}).then(this.onChatUpdated.bind(this, id));
|
||||
}
|
||||
|
||||
public deleteChat(id: number) {
|
||||
//return this.leaveChat(id).then(() => {
|
||||
return apiManager.invokeApi('messages.deleteChat', {
|
||||
chat_id: id
|
||||
});
|
||||
//});
|
||||
}
|
||||
|
||||
public migrateChat(id: number): Promise<number> {
|
||||
const chat: Chat = this.getChat(id);
|
||||
if(chat._ === 'channel') return Promise.resolve(chat.id);
|
||||
|
@ -39,6 +39,7 @@ import appNavigationController from '../../components/appNavigationController';
|
||||
import appNotificationsManager from './appNotificationsManager';
|
||||
import AppPrivateSearchTab from '../../components/sidebarRight/tabs/search';
|
||||
import { i18n } from '../langPack';
|
||||
import { SendMessageAction } from '../../layer';
|
||||
|
||||
//console.log('appImManager included33!');
|
||||
|
||||
@ -742,6 +743,26 @@ export class AppImManager {
|
||||
this.setInnerPeer(peerId, undefined, 'scheduled');
|
||||
}
|
||||
|
||||
private getTypingElement(action: SendMessageAction) {
|
||||
const el = document.createElement('span');
|
||||
el.classList.add('peer-typing');
|
||||
switch(action._) {
|
||||
//case 'sendMessageTypingAction': {
|
||||
default: {
|
||||
const c = 'peer-typing-text';
|
||||
el.classList.add(c);
|
||||
for(let i = 0; i < 3; ++i) {
|
||||
const dot = document.createElement('span');
|
||||
dot.className = c + '-dot';
|
||||
el.append(dot);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return el;
|
||||
}
|
||||
|
||||
public async getPeerStatus(peerId: number) {
|
||||
let subtitle: HTMLElement;
|
||||
if(!peerId) return '';
|
||||
@ -777,7 +798,7 @@ export class AppImManager {
|
||||
if(typings && typings.length) {
|
||||
const span = document.createElement('span');
|
||||
span.classList.add('online');
|
||||
span.append(i18n('Peer.Activity.User.TypingText'));
|
||||
span.append(this.getTypingElement(typings[0].action), i18n('Peer.Activity.User.TypingText'));
|
||||
return span;
|
||||
} else if(user.status?._ === 'userStatusOnline') {
|
||||
const span = document.createElement('span');
|
||||
|
@ -1991,9 +1991,10 @@ export class AppMessagesManager {
|
||||
});
|
||||
}
|
||||
|
||||
private doFlushHistory(inputPeer: any, justClear?: true): Promise<true> {
|
||||
private doFlushHistory(inputPeer: any, justClear?: boolean, revoke?: boolean): Promise<true> {
|
||||
return apiManager.invokeApi('messages.deleteHistory', {
|
||||
just_clear: justClear,
|
||||
revoke: revoke,
|
||||
peer: inputPeer,
|
||||
max_id: 0
|
||||
}).then((affectedHistory) => {
|
||||
@ -2014,7 +2015,7 @@ export class AppMessagesManager {
|
||||
})
|
||||
}
|
||||
|
||||
public async flushHistory(peerId: number, justClear?: true) {
|
||||
public async flushHistory(peerId: number, justClear?: boolean, revoke?: boolean) {
|
||||
if(appPeersManager.isChannel(peerId)) {
|
||||
const promise = this.getHistory(peerId, 0, 1);
|
||||
|
||||
@ -2039,7 +2040,7 @@ export class AppMessagesManager {
|
||||
});
|
||||
}
|
||||
|
||||
return this.doFlushHistory(appPeersManager.getInputPeerById(peerId), justClear).then(() => {
|
||||
return this.doFlushHistory(appPeersManager.getInputPeerById(peerId), justClear, revoke).then(() => {
|
||||
delete this.historiesStorage[peerId];
|
||||
delete this.messagesStorageByPeerId[peerId];
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { MOUNT_CLASS_TO } from "../../config/debug";
|
||||
import { isObject } from "../../helpers/object";
|
||||
import { DialogPeer, InputDialogPeer, InputNotifyPeer, InputPeer, Peer, Update } from "../../layer";
|
||||
import { LangPackKey } from "../langPack";
|
||||
import { RichTextProcessor } from "../richtextprocessor";
|
||||
import rootScope from "../rootScope";
|
||||
import appChatsManager from "./appChatsManager";
|
||||
@ -275,14 +276,19 @@ export class AppPeersManager {
|
||||
}
|
||||
}
|
||||
|
||||
public getDeleteButtonText(peerId: number) {
|
||||
public getDeleteButtonText(peerId: number): LangPackKey {
|
||||
switch(this.getDialogType(peerId)) {
|
||||
case 'megagroup':
|
||||
case 'channel':
|
||||
return 'Leave';
|
||||
return 'ChatList.Context.LeaveChannel';
|
||||
|
||||
case 'megagroup':
|
||||
return 'ChatList.Context.LeaveGroup';
|
||||
|
||||
case 'group':
|
||||
return 'ChatList.Context.DeleteAndExit';
|
||||
|
||||
default:
|
||||
return 'Delete';
|
||||
return 'ChatList.Context.DeleteChat';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -123,7 +123,7 @@ export class AppStickersManager {
|
||||
}
|
||||
|
||||
public getStickerSetThumbDownloadOptions(stickerSet: StickerSet.stickerSet) {
|
||||
const thumb = stickerSet.thumb as PhotoSize.photoSize;
|
||||
const thumb = stickerSet.thumbs[0] as PhotoSize.photoSize;
|
||||
const dcId = stickerSet.thumb_dc_id;
|
||||
|
||||
const isAnimated = stickerSet.pFlags?.animated;
|
||||
|
File diff suppressed because one or more lines are too long
@ -276,7 +276,7 @@ class TLSerialization {
|
||||
|
||||
this.storeInt(methodData.id, methodName + '[id]');
|
||||
|
||||
const pFlags = params.pFlags;
|
||||
const pFlags = params.pFlags || params; // * support pFlags, though am not expecting it to be there
|
||||
const flagsOffsets: {[paramName: string]: number} = {};
|
||||
//console.log('storeMethod', len, methodData);
|
||||
for(const param of methodData.params) {
|
||||
@ -287,7 +287,7 @@ class TLSerialization {
|
||||
const fieldBit = condType[0].split('.');
|
||||
|
||||
if(!(params[fieldBit[0]] & (1 << +fieldBit[1]))) {
|
||||
if((condType[1] === 'true' && pFlags && pFlags[param.name]) || params[param.name] !== undefined) {
|
||||
if(condType[1] === 'true' ? pFlags[param.name] : params[param.name] !== undefined) {
|
||||
//console.log('storeMethod autocompleting', methodName, param.name, params[param.name], type);
|
||||
params[fieldBit[0]] |= 1 << +fieldBit[1];
|
||||
} else {
|
||||
@ -399,7 +399,7 @@ class TLSerialization {
|
||||
//console.log('storeObject fieldBit', fieldBit, obj[fieldBit[0]]);
|
||||
|
||||
if(!(obj[fieldBit[0]] & (1 << +fieldBit[1]))) {
|
||||
if((condType[1] === 'true' && pFlags && pFlags[param.name]) || obj[param.name] !== undefined) {
|
||||
if(condType[1] === 'true' ? pFlags && pFlags[param.name] : obj[param.name] !== undefined) {
|
||||
//console.log('storeObject autocompleting', param.name, obj[param.name], type);
|
||||
obj[fieldBit[0]] |= 1 << +fieldBit[1];
|
||||
} else {
|
||||
|
@ -63,15 +63,16 @@ function camelizeName(string, camelizeFirstLetterIfFound, camelizeFirstLetterIfN
|
||||
});
|
||||
}
|
||||
|
||||
/** @type {(type: string) => any} */
|
||||
const processParamType = (type) => {
|
||||
/** @type {(type: string, parseBooleanFlags: boolean) => any} */
|
||||
const processParamType = (type, parseBooleanFlags) => {
|
||||
const isAdditional = type.indexOf('flags.-1?') === 0;
|
||||
if(type.includes('?')) {
|
||||
const isFlag = type.includes('?');
|
||||
if(isFlag) {
|
||||
type = type.split('?')[1];
|
||||
}
|
||||
|
||||
if(type.includes('Vector')) {
|
||||
return `Array<${processParamType(type.slice(7, -1))}>`;
|
||||
return `Array<${processParamType(type.slice(7, -1), parseBooleanFlags)}>`;
|
||||
}
|
||||
|
||||
switch(type) {
|
||||
@ -80,7 +81,7 @@ const processParamType = (type) => {
|
||||
return 'number';
|
||||
|
||||
case 'true':
|
||||
return 'true';
|
||||
return parseBooleanFlags ? 'true' : 'boolean';
|
||||
|
||||
case 'Bool':
|
||||
return 'boolean';
|
||||
@ -121,7 +122,7 @@ const processParams = (params, object = {}, parseBooleanFlags = true) => {
|
||||
type = replace[name];
|
||||
}
|
||||
|
||||
const processed = processParamType(type);
|
||||
const processed = processParamType(type, parseBooleanFlags);
|
||||
if(type.includes('?true') && parseBooleanFlags) {
|
||||
if(!object.pFlags) object.pFlags = {};
|
||||
object.pFlags[name] = processed;
|
||||
@ -236,7 +237,7 @@ mtproto.methods.forEach((_method) => {
|
||||
|
||||
const camelizedMethod = camelizeName(method, true, true);
|
||||
|
||||
methodsMap[method] = {req: camelizedMethod, res: processParamType(type)};
|
||||
methodsMap[method] = {req: camelizedMethod, res: processParamType(type, false)};
|
||||
|
||||
let str = `export type ${camelizedMethod} = {\n`;
|
||||
|
||||
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
107
src/scss/partials/_peerTyping.scss
Normal file
107
src/scss/partials/_peerTyping.scss
Normal file
@ -0,0 +1,107 @@
|
||||
.peer-typing {
|
||||
display: inline-block;
|
||||
margin-right: 4px;
|
||||
|
||||
&-text {
|
||||
&-dot {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
border-radius: 50%;
|
||||
background-color: $color-blue;
|
||||
margin: 0 1px;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
animation-duration: .6s;
|
||||
animation-iteration-count: infinite;
|
||||
animation-timing-function: linear;
|
||||
animation-name: dotMiddle;
|
||||
}
|
||||
|
||||
&-dot:first-child {
|
||||
animation-name: dotFirst;
|
||||
}
|
||||
|
||||
&-dot:last-child {
|
||||
animation-name: dotLast;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$scale-max: 1;
|
||||
$scale-step: 1 / 6;
|
||||
$scale-mid: $scale-max - $scale-step;
|
||||
$scale-min: $scale-max - ($scale-step * 2);
|
||||
$opacity-max: 1;
|
||||
$opacity-step: .1;
|
||||
$opacity-mid: $opacity-max - $opacity-step;
|
||||
$opacity-min: $opacity-max - ($opacity-step * 2);
|
||||
@keyframes dotFirst {
|
||||
0% {
|
||||
transform: scale($scale-min);
|
||||
opacity: $opacity-min;
|
||||
}
|
||||
|
||||
50% {
|
||||
transform: scale($scale-min);
|
||||
opacity: $opacity-min;
|
||||
}
|
||||
|
||||
75% {
|
||||
transform: scale($scale-max);
|
||||
opacity: $opacity-max;
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: scale($scale-min);
|
||||
opacity: $opacity-min;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes dotMiddle {
|
||||
0% {
|
||||
transform: scale($scale-mid);
|
||||
opacity: $opacity-mid;
|
||||
}
|
||||
|
||||
12.5% {
|
||||
transform: scale($scale-min);
|
||||
opacity: $opacity-min;
|
||||
}
|
||||
|
||||
62.5% {
|
||||
transform: scale($scale-min);
|
||||
opacity: $opacity-min;
|
||||
}
|
||||
|
||||
87.5% {
|
||||
transform: scale($scale-max);
|
||||
opacity: $opacity-max;
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: scale($scale-mid);
|
||||
opacity: $opacity-mid;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes dotLast {
|
||||
0% {
|
||||
transform: scale($scale-max);
|
||||
opacity: $opacity-max;
|
||||
}
|
||||
|
||||
25% {
|
||||
transform: scale($scale-min);
|
||||
opacity: $opacity-min;
|
||||
}
|
||||
|
||||
75% {
|
||||
transform: scale($scale-min);
|
||||
opacity: $opacity-min;
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: scale($scale-max);
|
||||
opacity: $opacity-max;
|
||||
}
|
||||
}
|
@ -6,8 +6,8 @@
|
||||
&-container {
|
||||
// max-width: 300px;
|
||||
// max-height: 424px;
|
||||
min-width: 300px;
|
||||
width: 300px;
|
||||
/* min-width: 300px;
|
||||
width: 300px; */
|
||||
padding: 12px 14px;
|
||||
|
||||
@media (min-height: 470px) {
|
||||
|
@ -11,6 +11,7 @@
|
||||
|
||||
&-container {
|
||||
padding: 1rem 1.5rem .75rem 1rem;
|
||||
max-width: unquote('min(400px, 100%)');
|
||||
}
|
||||
|
||||
&-title {
|
||||
@ -46,10 +47,16 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.popup-peer, .popup-confirm-action {
|
||||
.popup-container {
|
||||
max-width: unquote('min(400px, 100%)');
|
||||
.checkbox-field {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 3.5rem;
|
||||
padding: 0 1.1875rem;
|
||||
margin: 0;
|
||||
|
||||
.checkbox-box {
|
||||
left: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -133,10 +133,4 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.popup-delete-chat {
|
||||
.popup-container {
|
||||
max-width: 328px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -137,6 +137,7 @@ $chat-padding-handhelds: .5rem;
|
||||
@import "partials/document";
|
||||
@import "partials/audio";
|
||||
@import "partials/quizHint";
|
||||
@import "partials/peerTyping";
|
||||
|
||||
@import "partials/popups/popup";
|
||||
@import "partials/popups/editAvatar";
|
||||
|
Loading…
Reference in New Issue
Block a user