Browse Source

Layer 125

Refactor chat deleting
More translations
Typing text animation
master
Eduard Kuzmenko 3 years ago
parent
commit
081ce3e316
  1. 5
      src/components/buttonMenu.ts
  2. 4
      src/components/chat/bubbles.ts
  3. 24
      src/components/checkboxField.ts
  4. 5
      src/components/dialogsContextMenu.ts
  5. 2
      src/components/emoticonsDropdown/tabs/stickers.ts
  6. 3
      src/components/popups/avatar.ts
  7. 19
      src/components/popups/confirmAction.ts
  8. 46
      src/components/popups/datePicker.ts
  9. 136
      src/components/popups/deleteDialog.ts
  10. 77
      src/components/popups/deleteMessages.ts
  11. 4
      src/components/popups/index.ts
  12. 75
      src/components/popups/peer.ts
  13. 67
      src/components/popups/unpinMessage.ts
  14. 51
      src/components/sidebarLeft/tabs/2fa/email.ts
  15. 27
      src/components/sidebarLeft/tabs/2fa/index.ts
  16. 68
      src/components/sidebarLeft/tabs/activeSessions.ts
  17. 2
      src/components/sidebarRight/tabs/editContact.ts
  18. 41
      src/components/sidebarRight/tabs/editGroup.ts
  19. 33
      src/components/sidebarRight/tabs/groupType.ts
  20. 53
      src/lang.ts
  21. 1064
      src/layer.d.ts
  22. 33
      src/lib/appManagers/appChatsManager.ts
  23. 23
      src/lib/appManagers/appImManager.ts
  24. 7
      src/lib/appManagers/appMessagesManager.ts
  25. 14
      src/lib/appManagers/appPeersManager.ts
  26. 2
      src/lib/appManagers/appStickersManager.ts
  27. 4
      src/lib/mtproto/schema.ts
  28. 6
      src/lib/mtproto/tl_utils.ts
  29. 15
      src/scripts/generate_mtproto_types.js
  30. 2
      src/scripts/in/schema.json
  31. 2
      src/scripts/out/schema.json
  32. 107
      src/scss/partials/_peerTyping.scss
  33. 4
      src/scss/partials/popups/_datePicker.scss
  34. 15
      src/scss/partials/popups/_peer.scss
  35. 6
      src/scss/partials/popups/_popup.scss
  36. 1
      src/scss/style.scss

5
src/components/buttonMenu.ts

@ -19,9 +19,8 @@ const ButtonMenuItem = (options: ButtonMenuItemOptions) => {
const {icon, text, onClick} = options; const {icon, text, onClick} = options;
const el = document.createElement('div'); const el = document.createElement('div');
el.className = 'btn-menu-item tgico-' + icon; el.className = 'btn-menu-item tgico-' + icon;
el.append(i18n(text));
ripple(el); ripple(el);
el.append(i18n(text));
// * cancel keyboard close // * cancel keyboard close
attachClickEvent(el, CLICK_EVENT_NAME !== 'click' ? (e) => { attachClickEvent(el, CLICK_EVENT_NAME !== 'click' ? (e) => {
@ -54,4 +53,4 @@ const ButtonMenu = (buttons: ButtonMenuItemOptions[], listenerSetter?: ListenerS
return el; return el;
}; };
export default ButtonMenu; export default ButtonMenu;

4
src/components/chat/bubbles.ts

@ -811,7 +811,7 @@ export default class ChatBubbles {
if(['IMG', 'DIV', "AVATAR-ELEMENT", 'SPAN'/* , 'A' */].indexOf(target.tagName) === -1) target = findUpTag(target, 'DIV'); 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')) { if(target.classList.contains('goto-original')) {
const savedFrom = bubble.dataset.savedFrom; const savedFrom = bubble.dataset.savedFrom;
const splitted = savedFrom.split('_'); const splitted = savedFrom.split('_');
@ -2420,7 +2420,7 @@ export default class ChatBubbles {
} else { } 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>`; /* 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.innerHTML = fromTitle + 'Forwarded from ' + title; */
nameDiv.append('Forwarded from ', title); nameDiv.append(i18n('ForwardedFrom', [title]));
if(savedFrom) { if(savedFrom) {
nameDiv.dataset.savedFrom = savedFrom; nameDiv.dataset.savedFrom = savedFrom;

24
src/components/checkboxField.ts

@ -3,21 +3,23 @@ import { getDeepProperty } from "../helpers/object";
import { ripple } from "./ripple"; import { ripple } from "./ripple";
import { LangPackKey, _i18n } from "../lib/langPack"; 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 { export default class CheckboxField {
public input: HTMLInputElement; public input: HTMLInputElement;
public label: HTMLLabelElement; public label: HTMLLabelElement;
public span: HTMLSpanElement; public span: HTMLSpanElement;
constructor(options: { constructor(options: CheckboxFieldOptions = {}) {
text?: LangPackKey,
name?: string,
round?: boolean,
stateKey?: string,
disabled?: boolean,
checked?: boolean,
restriction?: boolean,
withRipple?: boolean
} = {}) {
const label = this.label = document.createElement('label'); const label = this.label = document.createElement('label');
label.classList.add('checkbox-field'); label.classList.add('checkbox-field');
@ -57,7 +59,7 @@ export default class CheckboxField {
if(options.text) { if(options.text) {
span = this.span = document.createElement('span'); span = this.span = document.createElement('span');
span.classList.add('checkbox-caption'); span.classList.add('checkbox-caption');
_i18n(span, options.text); _i18n(span, options.text, options.textArgs);
} else { } else {
label.classList.add('checkbox-without-caption'); label.classList.add('checkbox-without-caption');
} }

5
src/components/dialogsContextMenu.ts

@ -6,6 +6,7 @@ import { findUpTag } from "../helpers/dom";
import { positionMenu, openBtnMenu } from "./misc"; import { positionMenu, openBtnMenu } from "./misc";
import ButtonMenu, { ButtonMenuItemOptions } from "./buttonMenu"; import ButtonMenu, { ButtonMenuItemOptions } from "./buttonMenu";
import PopupDeleteDialog from "./popups/deleteDialog"; import PopupDeleteDialog from "./popups/deleteDialog";
import { i18n } from "../lib/langPack";
export default class DialogsContextMenu { export default class DialogsContextMenu {
private element: HTMLElement; private element: HTMLElement;
@ -150,7 +151,7 @@ export default class DialogsContextMenu {
}); });
// delete button // 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'); li.classList.add('menu-open');
positionMenu(e, this.element); positionMenu(e, this.element);
@ -159,4 +160,4 @@ export default class DialogsContextMenu {
this.selectedId = this.dialog = this.filterId = undefined; this.selectedId = this.dialog = this.filterId = undefined;
}); });
}; };
} }

2
src/components/emoticonsDropdown/tabs/stickers.ts

@ -200,7 +200,7 @@ export default class StickersTab implements EmoticonsTab {
//console.log('got stickerSet', stickerSet, li); //console.log('got stickerSet', stickerSet, li);
if(stickerSet.set.thumb) { if(stickerSet.set.thumbs?.length) {
const downloadOptions = appStickersManager.getStickerSetThumbDownloadOptions(stickerSet.set); const downloadOptions = appStickersManager.getStickerSetThumbDownloadOptions(stickerSet.set);
const promise = appDownloadManager.download(downloadOptions); const promise = appDownloadManager.download(downloadOptions);

3
src/components/popups/avatar.ts

@ -2,6 +2,7 @@ import appDownloadManager from "../../lib/appManagers/appDownloadManager";
import resizeableImage from "../../lib/cropper"; import resizeableImage from "../../lib/cropper";
import PopupElement from "."; import PopupElement from ".";
import { ripple } from "../ripple"; import { ripple } from "../ripple";
import { _i18n } from "../../lib/langPack";
export default class PopupAvatar extends PopupElement { export default class PopupAvatar extends PopupElement {
private cropContainer: HTMLElement; private cropContainer: HTMLElement;
@ -24,7 +25,7 @@ export default class PopupAvatar extends PopupElement {
super('popup-avatar', null, {closable: true}); super('popup-avatar', null, {closable: true});
this.h6 = document.createElement('h6'); this.h6 = document.createElement('h6');
this.h6.innerText = 'Drag to Reposition'; _i18n(this.h6, 'Popup.Avatar.Title');
this.btnClose.classList.remove('btn-icon'); this.btnClose.classList.remove('btn-icon');

19
src/components/popups/confirmAction.ts

@ -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);
}
}

46
src/components/popups/datePicker.ts

@ -1,7 +1,7 @@
import PopupElement, { PopupOptions } from "."; import PopupElement, { PopupOptions } from ".";
import { getFullDate } from "../../helpers/date"; import { getFullDate } from "../../helpers/date";
import mediaSizes from "../../helpers/mediaSizes"; import mediaSizes from "../../helpers/mediaSizes";
import I18n from "../../lib/langPack"; import I18n, { i18n, LangPackKey } from "../../lib/langPack";
import InputField from "../inputField"; import InputField from "../inputField";
export default class PopupDatePicker extends PopupElement { export default class PopupDatePicker extends PopupElement {
@ -237,23 +237,47 @@ export default class PopupDatePicker extends PopupElement {
public setTimeTitle() { public setTimeTitle() {
if(this.btnConfirm && this.selectedDate) { if(this.btnConfirm && this.selectedDate) {
let dayStr = ''; let key: LangPackKey, args: any[] = [];
const date = new Date(); const date = new Date();
date.setHours(0, 0, 0, 0); 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()) { if(this.selectedDate.getTime() === date.getTime()) {
dayStr = 'Today'; key = 'Schedule.SendToday';
} else if(this.selectedDate.getTime() === (date.getTime() + 86400e3)) { }/* else if(this.selectedDate.getTime() === (date.getTime() + 86400e3)) {
dayStr = 'Tomorrow'; dayStr = 'Tomorrow';
} else { } */ else {
dayStr = 'on ' + getFullDate(this.selectedDate, { key = 'Schedule.SendDate';
noTime: true,
monthAsNumber: true, const dateOptions: Intl.DateTimeFormatOptions = {
leadingZero: true 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));
} }
} }

136
src/components/popups/deleteDialog.ts

@ -1,28 +1,51 @@
import { PopupButton } from ".";
import appChatsManager from "../../lib/appManagers/appChatsManager"; import appChatsManager from "../../lib/appManagers/appChatsManager";
import appMessagesManager from "../../lib/appManagers/appMessagesManager"; import appMessagesManager from "../../lib/appManagers/appMessagesManager";
import appPeersManager, { PeerType } from "../../lib/appManagers/appPeersManager"; 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 { export default class PopupDeleteDialog {
constructor(peerId: number, peerType: PeerType = appPeersManager.getDialogType(peerId)) { constructor(peerId: number, peerType: PeerType = appPeersManager.getDialogType(peerId), onSelect?: (promise: Promise<any>) => void) {
let firstName = appPeersManager.getPeerTitle(peerId, false, true); const peerTitleElement = new PeerTitle({
peerId,
onlyFirstName: true
}).element;
/* const callbackFlush = (checked: PopupPeerButtonCallbackCheckboxes) => {
const promise = appMessagesManager.flushHistory(peerId, checkboxes ? !checked[checkboxes[0].text] : undefined);
onSelect && onSelect(promise);
}; */
let callbackFlush = (justClear?: true) => { const callbackLeave = (checked: PopupPeerButtonCallbackCheckboxes) => {
appMessagesManager.flushHistory(peerId, justClear); const promise = appChatsManager.leave(-peerId);
onSelect && onSelect(promise);
}; };
let callbackLeave = () => { const callbackDelete = (checked: PopupPeerButtonCallbackCheckboxes) => {
appChatsManager.leave(-peerId); 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) { switch(peerType) {
case 'channel': { case 'channel': {
title = 'Leave Channel?'; title = 'LeaveChannelMenu';
description = `Are you sure you want to leave this channel?`; description = 'ChannelLeaveAlertWithName';
descriptionArgs = [peerTitleElement];
buttons = [{ buttons = [{
text: 'LEAVE ' + firstName, langKey: 'LeaveChannel',
isDanger: true, isDanger: true,
callback: callbackLeave callback: callbackLeave
}]; }];
@ -30,71 +53,90 @@ export default class PopupDeleteDialog {
break; break;
} }
case 'megagroup': { /* case 'megagroup': {
title = 'Leave Group?'; title = 'Leave Group?';
description = `Are you sure you want to leave this group?`; description = `Are you sure you want to leave this group?`;
buttons = [{ buttons = [{
text: 'LEAVE ' + firstName, text: 'LEAVE ' + peerTitleElement,
isDanger: true, isDanger: true,
callback: callbackLeave callback: callbackLeave
}]; }];
break; break;
} } */
case 'chat': { case 'chat': {
title = 'Delete Chat?'; title = 'DeleteChatUser';
description = `Are you sure you want to delete chat with <b>${firstName}</b>?`; description = 'AreYouSureDeleteThisChatWithUser';
descriptionArgs = [peerTitleElement];
checkboxes = [{
text: 'DeleteMessagesOptionAlso',
textArgs: [
new PeerTitle({
peerId,
onlyFirstName: true
}).element
]
}];
buttons = [{ buttons = [{
text: 'DELETE FOR ME AND ' + firstName, langKey: 'DeleteChatUser',
isDanger: true, isDanger: true,
callback: () => callbackFlush() callback: callbackDelete
}, {
text: 'DELETE JUST FOR ME',
isDanger: true,
callback: () => callbackFlush(true)
}]; }];
break; break;
} }
case 'saved': { case 'saved': {
title = 'Delete Saved Messages?'; title = 'DeleteChatUser';
description = `Are you sure you want to delete all your saved messages?`; description = 'AreYouSureDeleteThisChatSavedMessages';
buttons = [{ buttons = [{
text: 'DELETE SAVED MESSAGES', langKey: 'DeleteChatUser',
isDanger: true, isDanger: true,
callback: () => callbackFlush() callback: callbackDelete
}]; }];
break; break;
} }
case 'megagroup':
case 'group': { case 'group': {
title = 'Delete and leave Group?'; if(appChatsManager.hasRights(-peerId, 'delete_chat')) {
description = `Are you sure you want to delete all message history and leave <b>${firstName}</b>?`; title = 'DeleteMegaMenu';
buttons = [{ description = 'AreYouSureDeleteAndExit';
text: 'DELETE AND LEAVE ' + firstName, buttons = [{
isDanger: true, langKey: 'DeleteMegaMenu',
callback: () => callbackLeave() isDanger: true,
}]; callback: callbackDelete
}];
checkboxes = [{
text: 'DeleteChat.DeleteGroupForAll'
}];
} else {
title = 'LeaveMegaMenu';
description = 'AreYouSureDeleteAndExitName';
descriptionArgs = [peerTitleElement];
buttons = [{
langKey: 'DeleteChatUser',
isDanger: true,
callback: callbackLeave
}];
}
break; break;
} }
} }
buttons.push({ new PopupPeer('popup-delete-chat', {
text: 'CANCEL',
isCancel: true
});
let popup = new PopupPeer('popup-delete-chat', {
peerId, peerId,
title, titleLangKey: title,
description, descriptionLangKey: description,
buttons descriptionLangArgs: descriptionArgs,
}); buttons,
checkboxes
popup.show(); }).show();
} }
} }

77
src/components/popups/deleteMessages.ts

@ -1,14 +1,18 @@
import appChatsManager from "../../lib/appManagers/appChatsManager"; import appChatsManager from "../../lib/appManagers/appChatsManager";
import appMessagesManager from "../../lib/appManagers/appMessagesManager"; import appMessagesManager from "../../lib/appManagers/appMessagesManager";
import appPeersManager from "../../lib/appManagers/appPeersManager";
import rootScope from "../../lib/rootScope"; import rootScope from "../../lib/rootScope";
import { PopupButton } from "."; import { addCancelButton, PopupButton } from ".";
import PopupPeer from "./peer"; import PopupPeer from "./peer";
import { ChatType } from "../chat/chat"; import { ChatType } from "../chat/chat";
import { i18n, LangPackKey } from "../../lib/langPack";
import PeerTitle from "../peerTitle";
export default class PopupDeleteMessages { export default class PopupDeleteMessages {
constructor(peerId: number, mids: number[], type: ChatType, onConfirm?: () => void) { 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(); mids = mids.slice();
const callback = (revoke?: true) => { const callback = (revoke?: true) => {
@ -20,26 +24,29 @@ export default class PopupDeleteMessages {
} }
}; };
let title: string, description: string, buttons: PopupButton[]; let title: LangPackKey, titleArgs: any[], description: LangPackKey, descriptionArgs: any[], buttons: PopupButton[];
title = `Delete ${mids.length === 1 ? '' : mids.length + ' '}Message${mids.length === 1 ? '' : 's'}?`; if(mids.length === 1) {
description = `Are you sure you want to delete ${mids.length === 1 ? 'this message' : 'these messages'}?`; 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') { if(peerId === rootScope.myId || type === 'scheduled') {
buttons = [{
text: 'DELETE',
isDanger: true,
callback: () => callback()
}];
} else { } else {
buttons = [{
text: 'DELETE JUST FOR ME',
isDanger: true,
callback: () => callback()
}];
if(peerId > 0) { if(peerId > 0) {
buttons.push({ buttons.push({
text: 'DELETE FOR ME AND ' + firstName, langKey: 'DeleteMessagesOptionAlso',
langArgs: [peerTitleElement],
isDanger: true, isDanger: true,
callback: () => callback(true) callback: () => callback(true)
}); });
@ -56,47 +63,39 @@ export default class PopupDeleteMessages {
if(canRevoke.length) { if(canRevoke.length) {
if(canRevoke.length === mids.length) { if(canRevoke.length === mids.length) {
buttons.push({ buttons.push({
text: 'DELETE FOR ALL', langKey: 'DeleteForAll',
isDanger: true, isDanger: true,
callback: () => callback(true) callback: () => callback(true)
}); });
} else { } else {
const buttonText = 'Unsend my and delete';
buttons.push({ buttons.push({
text: buttonText, langKey: 'DeleteMessagesOption',
isDanger: true, isDanger: true,
callback: () => callback(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 { } else {
//if(!hasRights || appChatsManager.isBroadcast(-peerId) || appChatsManager.isMegagroup(-peerId)) { buttons[0].callback = () => callback(true);
buttons.shift();
//}
buttons.push({
text: 'DELETE FOR ALL',
isDanger: true,
callback: () => callback(true)
});
} }
} }
} }
buttons.push({ addCancelButton(buttons);
text: 'CANCEL',
isCancel: true
});
const popup = new PopupPeer('popup-delete-chat', { const popup = new PopupPeer('popup-delete-chat', {
peerId, peerId,
title, titleLangKey: title,
description, titleLangArgs: titleArgs,
descriptionLangKey: description,
descriptionLangArgs: descriptionArgs,
buttons buttons
}); });
popup.show(); popup.show();
} }
} }

4
src/components/popups/index.ts

@ -93,13 +93,13 @@ export default class PopupElement {
const button = document.createElement('button'); const button = document.createElement('button');
button.className = 'btn' + (b.isDanger ? ' danger' : ' primary'); button.className = 'btn' + (b.isDanger ? ' danger' : ' primary');
ripple(button);
if(b.text) { if(b.text) {
button.innerHTML = b.text; button.innerHTML = b.text;
} else { } else {
button.append(i18n(b.langKey, b.langArgs)); button.append(i18n(b.langKey, b.langArgs));
} }
ripple(button);
if(b.callback) { if(b.callback) {
button.addEventListener('click', () => { button.addEventListener('click', () => {

75
src/components/popups/peer.ts

@ -1,32 +1,67 @@
import AvatarElement from "../avatar"; import AvatarElement from "../avatar";
import PopupElement, { PopupButton } from "."; import PopupElement, { addCancelButton, PopupButton, PopupOptions } from ".";
import { i18n, LangPackKey } from "../../lib/langPack"; 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 { export default class PopupPeer extends PopupElement {
constructor(private className: string, options: Partial<{ constructor(private className: string, options: PopupPeerOptions = {}) {
peerId: number, super('popup-peer' + (className ? ' ' + className : ''), addCancelButton(options.buttons), {overlayClosable: true, ...options});
title: string,
titleLangKey?: LangPackKey, if(options.peerId) {
description: string, let avatarEl = new AvatarElement();
descriptionLangKey?: LangPackKey, avatarEl.setAttribute('dialog', '1');
buttons: Array<PopupButton> avatarEl.setAttribute('peer', '' + options.peerId);
}> = {}) { avatarEl.classList.add('avatar-32');
super('popup-peer' + (className ? ' ' + className : ''), options.buttons, {overlayClosable: true}); this.header.prepend(avatarEl);
}
let avatarEl = new AvatarElement();
avatarEl.setAttribute('dialog', '1'); if(options.descriptionLangKey) this.title.append(i18n(options.titleLangKey, options.titleLangArgs));
avatarEl.setAttribute('peer', '' + options.peerId);
avatarEl.classList.add('avatar-32');
if(options.descriptionLangKey) this.title.append(i18n(options.titleLangKey));
else this.title.innerText = options.title || ''; else this.title.innerText = options.title || '';
this.header.prepend(avatarEl);
let p = document.createElement('p'); let p = document.createElement('p');
p.classList.add('popup-description'); 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; 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);
} }
} }

67
src/components/popups/unpinMessage.ts

@ -1,12 +1,15 @@
import appMessagesManager from "../../lib/appManagers/appMessagesManager"; import appMessagesManager from "../../lib/appManagers/appMessagesManager";
import { PopupButton } from "."; import { addCancelButton, PopupButton } from ".";
import PopupPeer from "./peer"; import PopupPeer from "./peer";
import appPeersManager from "../../lib/appManagers/appPeersManager"; import appPeersManager from "../../lib/appManagers/appPeersManager";
import rootScope from "../../lib/rootScope"; import rootScope from "../../lib/rootScope";
import { LangPackKey } from "../../lib/langPack";
import appChatsManager from "../../lib/appManagers/appChatsManager";
import PeerTitle from "../peerTitle";
export default class PopupPinMessage { export default class PopupPinMessage {
constructor(peerId: number, mid: number, unpin?: true, onConfirm?: () => void) { 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); const canUnpin = appPeersManager.canPinMessage(peerId);
@ -29,77 +32,79 @@ export default class PopupPinMessage {
}, 300); }, 300);
}; };
const firstName = appPeersManager.getPeerTitle(peerId, false, true);
if(unpin) { if(unpin) {
let buttonText = 'UNPIN'; let buttonText: LangPackKey = 'UnpinMessage';
if(!mid) { if(!mid) {
if(canUnpin) { if(canUnpin) {
title = 'Unpin All Messages?'; title = 'Popup.Unpin.AllTitle';
description = 'Would you like to unpin all messages?'; description = 'Chat.UnpinAllMessagesConfirmation';
} else { } else {
title = 'Hide Pinned Messages?'; title = 'Popup.Unpin.HideTitle';
description = 'Do you want to hide the pinned message bar? It wil stay hidden until a new message is pinned.'; description = 'Popup.Unpin.HideDescription';
buttonText = 'HIDE'; buttonText = 'Popup.Unpin.Hide';
} }
} else { } else {
title = `Unpin Message?`; title = 'UnpinMessageAlertTitle';
description = 'Would you like to unpin this message?'; description = 'Chat.Confirm.Unpin';
} }
buttons.push({ buttons.push({
text: buttonText, langKey: buttonText,
isDanger: true, isDanger: true,
callback: () => callback() callback: () => callback()
}); });
} else { } else {
title = 'Pin Message?'; title = 'PinMessageAlertTitle';
const pinButtonText: LangPackKey = 'PinMessage';
if(peerId < 0) { if(peerId < 0) {
description = 'Do you want to pin this message for all members in the group?';
buttons.push({ buttons.push({
text: 'PIN AND NOTIFY', langKey: pinButtonText,
callback: () => callback() callback: () => callback()
}); });
buttons.push({ if(appChatsManager.isBroadcast(-peerId)) {
text: 'PIN WITHOUT NOTIFYING', description = 'PinMessageAlertChannel';
callback: () => callback(undefined, true) } else {
}); description = 'PinMessageAlert';
buttons.push({
langKey: 'PinNotify',
callback: () => callback(undefined, true)
});
}
} else { } else {
description = 'Would you like to pin this message?'; description = 'PinMessageAlertChat';
if(peerId === rootScope.myId) { if(peerId === rootScope.myId) {
buttons.push({ buttons.push({
text: 'PIN', langKey: pinButtonText,
callback: () => callback() callback: () => callback()
}); });
} else { } else {
buttons.push({ buttons.push({
text: 'PIN JUST FOR ME', langKey: pinButtonText,
callback: () => callback(true) callback: () => callback(true)
}); });
buttons.push({ buttons.push({
text: 'PIN FOR ME AND ' + firstName, langKey: 'PinAlsoFor',
langArgs: [new PeerTitle({peerId, onlyFirstName: true}).element],
callback: () => callback() callback: () => callback()
}); });
} }
} }
} }
buttons.push({ addCancelButton(buttons);
text: 'CANCEL',
isCancel: true
});
const popup = new PopupPeer('popup-delete-chat', { const popup = new PopupPeer('popup-delete-chat', {
peerId, peerId,
title, titleLangKey: title,
description, descriptionLangKey: description,
buttons buttons
}); });
popup.show(); popup.show();
} }
} }

51
src/components/sidebarLeft/tabs/2fa/email.ts

@ -6,12 +6,12 @@ import { SliderSuperTab } from "../../../slider";
import { wrapSticker } from "../../../wrappers"; import { wrapSticker } from "../../../wrappers";
import InputField from "../../../inputField"; import InputField from "../../../inputField";
import { attachClickEvent, cancelEvent, canFocus } from "../../../../helpers/dom"; import { attachClickEvent, cancelEvent, canFocus } from "../../../../helpers/dom";
import PopupConfirmAction from "../../../popups/confirmAction";
import { putPreloader } from "../../../misc"; import { putPreloader } from "../../../misc";
import passwordManager from "../../../../lib/mtproto/passwordManager"; import passwordManager from "../../../../lib/mtproto/passwordManager";
import AppTwoStepVerificationSetTab from "./passwordSet"; import AppTwoStepVerificationSetTab from "./passwordSet";
import AppTwoStepVerificationEmailConfirmationTab from "./emailConfirmation"; import AppTwoStepVerificationEmailConfirmationTab from "./emailConfirmation";
import RichTextProcessor from "../../../../lib/richtextprocessor"; import RichTextProcessor from "../../../../lib/richtextprocessor";
import PopupPeer from "../../../popups/peer";
export default class AppTwoStepVerificationEmailTab extends SliderSuperTab { export default class AppTwoStepVerificationEmailTab extends SliderSuperTab {
public inputField: InputField; public inputField: InputField;
@ -129,30 +129,31 @@ export default class AppTwoStepVerificationEmailTab extends SliderSuperTab {
}; };
attachClickEvent(btnSkip, (e) => { attachClickEvent(btnSkip, (e) => {
const popup = new PopupConfirmAction('popup-skip-email', [{ const popup = new PopupPeer('popup-skip-email', {
text: 'CANCEL', buttons: [{
isCancel: true text: 'CANCEL',
}, { isCancel: true
text: 'SKIP', }, {
callback: () => { text: 'SKIP',
//inputContent.classList.add('sidebar-left-section-disabled'); callback: () => {
toggleButtons(true); //inputContent.classList.add('sidebar-left-section-disabled');
putPreloader(btnSkip); toggleButtons(true);
passwordManager.updateSettings({ putPreloader(btnSkip);
hint: this.hint, passwordManager.updateSettings({
currentPassword: this.plainPassword, hint: this.hint,
newPassword: this.newPassword, currentPassword: this.plainPassword,
email: '' newPassword: this.newPassword,
}).then(() => { email: ''
goNext(); }).then(() => {
}, (err) => { goNext();
toggleButtons(false); }, (err) => {
}); toggleButtons(false);
}, });
isDanger: true, },
}], { 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.' 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(); popup.show();

27
src/components/sidebarLeft/tabs/2fa/index.ts

@ -4,7 +4,7 @@ import { AccountPassword } from "../../../../layer";
import appStickersManager from "../../../../lib/appManagers/appStickersManager"; import appStickersManager from "../../../../lib/appManagers/appStickersManager";
import passwordManager from "../../../../lib/mtproto/passwordManager"; import passwordManager from "../../../../lib/mtproto/passwordManager";
import Button from "../../../button"; import Button from "../../../button";
import PopupConfirmAction from "../../../popups/confirmAction"; import PopupPeer from "../../../popups/peer";
import { SliderSuperTab } from "../../../slider"; import { SliderSuperTab } from "../../../slider";
import { wrapSticker } from "../../../wrappers"; import { wrapSticker } from "../../../wrappers";
import AppSettingsTab from "../settings"; import AppSettingsTab from "../settings";
@ -62,18 +62,19 @@ export default class AppTwoStepVerificationTab extends SliderSuperTab {
}); });
attachClickEvent(btnDisablePassword, () => { attachClickEvent(btnDisablePassword, () => {
const popup = new PopupConfirmAction('popup-disable-password', [{ const popup = new PopupPeer('popup-disable-password', {
text: 'DISABLE', buttons: [{
callback: () => { text: 'DISABLE',
passwordManager.updateSettings({currentPassword: this.plainPassword}).then(() => { callback: () => {
this.slider.sliceTabsUntilTab(AppSettingsTab, this); passwordManager.updateSettings({currentPassword: this.plainPassword}).then(() => {
this.close(); this.slider.sliceTabsUntilTab(AppSettingsTab, this);
}); this.close();
}, });
isDanger: true, },
}], { isDanger: true,
title: 'Warning', }],
text: 'Are you sure you want to disable<br/>your password?' titleLangKey: 'Warning',
descriptionLangKey: 'Are you sure you want to disable<br/>your password?'
}); });
popup.show(); popup.show();

68
src/components/sidebarLeft/tabs/activeSessions.ts

@ -7,11 +7,11 @@ import { formatDateAccordingToToday } from "../../../helpers/date";
import { attachContextMenuListener, openBtnMenu, positionMenu } from "../../misc"; import { attachContextMenuListener, openBtnMenu, positionMenu } from "../../misc";
import { attachClickEvent, findUpClassName, toggleDisability } from "../../../helpers/dom"; import { attachClickEvent, findUpClassName, toggleDisability } from "../../../helpers/dom";
import ButtonMenu from "../../buttonMenu"; import ButtonMenu from "../../buttonMenu";
import PopupConfirmAction from "../../popups/confirmAction";
import apiManager from "../../../lib/mtproto/mtprotoworker"; import apiManager from "../../../lib/mtproto/mtprotoworker";
import { toast } from "../../toast"; import { toast } from "../../toast";
import AppPrivacyAndSecurityTab from "./privacyAndSecurity"; import AppPrivacyAndSecurityTab from "./privacyAndSecurity";
import I18n from "../../../lib/langPack"; import I18n from "../../../lib/langPack";
import PopupPeer from "../../popups/peer";
export default class AppActiveSessionsTab extends SliderSuperTab { export default class AppActiveSessionsTab extends SliderSuperTab {
public privacyTab: AppPrivacyAndSecurityTab; public privacyTab: AppPrivacyAndSecurityTab;
@ -56,23 +56,24 @@ export default class AppActiveSessionsTab extends SliderSuperTab {
if(authorizations.length) { if(authorizations.length) {
const btnTerminate = Button('btn-primary btn-transparent danger', {icon: 'stop', text: 'TerminateAllSessions'}); const btnTerminate = Button('btn-primary btn-transparent danger', {icon: 'stop', text: 'TerminateAllSessions'});
attachClickEvent(btnTerminate, (e) => { attachClickEvent(btnTerminate, (e) => {
new PopupConfirmAction('revoke-session', [{ new PopupPeer('revoke-session', {
langKey: 'Terminate', buttons: [{
isDanger: true, langKey: 'Terminate',
callback: () => { isDanger: true,
const toggle = toggleDisability([btnTerminate], true); callback: () => {
apiManager.invokeApi('auth.resetAuthorizations').then(value => { const toggle = toggleDisability([btnTerminate], true);
//toggleDisability([btnTerminate], false); apiManager.invokeApi('auth.resetAuthorizations').then(value => {
btnTerminate.remove(); //toggleDisability([btnTerminate], false);
otherSection.container.remove(); btnTerminate.remove();
this.privacyTab.updateActiveSessions(); otherSection.container.remove();
}, onError).finally(() => { this.privacyTab.updateActiveSessions();
toggle(); }, onError).finally(() => {
}); toggle();
} });
}], { }
title: 'AreYouSureSessionsTitle', }],
text: 'AreYouSureSessions' titleLangKey: 'AreYouSureSessionsTitle',
descriptionLangKey: 'AreYouSureSessions'
}).show(); }).show();
}); });
@ -106,21 +107,22 @@ export default class AppActiveSessionsTab extends SliderSuperTab {
const onTerminateClick = () => { const onTerminateClick = () => {
const hash = target.dataset.hash; const hash = target.dataset.hash;
new PopupConfirmAction('revoke-session', [{ new PopupPeer('revoke-session', {
langKey: 'Terminate', buttons: [{
isDanger: true, langKey: 'Terminate',
callback: () => { isDanger: true,
apiManager.invokeApi('account.resetAuthorization', {hash}) callback: () => {
.then(value => { apiManager.invokeApi('account.resetAuthorization', {hash})
if(value) { .then(value => {
target.remove(); if(value) {
this.privacyTab.updateActiveSessions(); target.remove();
} this.privacyTab.updateActiveSessions();
}, onError); }
} }, onError);
}], { }
title: 'AreYouSureSessionTitle', }],
text: 'TerminateSessionText' titleLangKey: 'AreYouSureSessionTitle',
descriptionLangKey: 'TerminateSessionText'
}).show(); }).show();
}; };

2
src/components/sidebarRight/tabs/editContact.ts

@ -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, () => { attachClickEvent(btnDelete, () => {
new PopupPeer('popup-delete-contact', { new PopupPeer('popup-delete-contact', {

41
src/components/sidebarRight/tabs/editGroup.ts

@ -15,6 +15,7 @@ import AppGroupTypeTab from "./groupType";
import rootScope from "../../../lib/rootScope"; import rootScope from "../../../lib/rootScope";
import AppGroupPermissionsTab from "./groupPermissions"; import AppGroupPermissionsTab from "./groupPermissions";
import { i18n } from "../../../lib/langPack"; import { i18n } from "../../../lib/langPack";
import PopupDeleteDialog from "../../popups/deleteDialog";
export default class AppEditGroupTab extends SliderSuperTab { export default class AppEditGroupTab extends SliderSuperTab {
private groupNameInputField: InputField; private groupNameInputField: InputField;
@ -196,42 +197,20 @@ export default class AppEditGroupTab extends SliderSuperTab {
this.scrollable.append(section.container); 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 section = new SettingSection({});
const btnDelete = Button('btn-primary btn-transparent danger', {icon: 'delete', text: 'DeleteMega'}); const btnDelete = Button('btn-primary btn-transparent danger', {icon: 'delete', text: 'DeleteMega'});
attachClickEvent(btnDelete, () => { attachClickEvent(btnDelete, () => {
new PopupPeer('popup-delete-group', { new PopupDeleteDialog(-this.chatId, undefined, (promise) => {
peerId: -this.chatId, const toggle = toggleDisability([btnDelete], true);
titleLangKey: 'DeleteMegaMenu', promise.then(() => {
descriptionLangKey: 'AreYouSureDeleteAndExit', this.close();
buttons: addCancelButton([{ }, () => {
langKey: 'DeleteMegaMenu', toggle();
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();
}, {listenerSetter: this.listenerSetter}); }, {listenerSetter: this.listenerSetter});
section.content.append(btnDelete); section.content.append(btnDelete);

33
src/components/sidebarRight/tabs/groupType.ts

@ -6,7 +6,6 @@ import appChatsManager from "../../../lib/appManagers/appChatsManager";
import appProfileManager from "../../../lib/appManagers/appProfileManager"; import appProfileManager from "../../../lib/appManagers/appProfileManager";
import Button from "../../button"; import Button from "../../button";
import { setButtonLoader } from "../../misc"; import { setButtonLoader } from "../../misc";
import PopupConfirmAction from "../../popups/confirmAction";
import RadioField from "../../radioField"; import RadioField from "../../radioField";
import Row, { RadioFormFromRows } from "../../row"; import Row, { RadioFormFromRows } from "../../row";
import { SettingSection } from "../../sidebarLeft"; import { SettingSection } from "../../sidebarLeft";
@ -14,6 +13,7 @@ import { toast } from "../../toast";
import { UsernameInputField } from "../../usernameInputField"; import { UsernameInputField } from "../../usernameInputField";
import { SliderSuperTabEventable } from "../../sliderTab"; import { SliderSuperTabEventable } from "../../sliderTab";
import I18n from "../../../lib/langPack"; import I18n from "../../../lib/langPack";
import PopupPeer from "../../popups/peer";
export default class AppGroupTypeTab extends SliderSuperTabEventable { export default class AppGroupTypeTab extends SliderSuperTabEventable {
public peerId: number; 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'}); const btnRevoke = Button('btn-primary btn-transparent danger', {icon: 'delete', text: 'RevokeLink'});
attachClickEvent(btnRevoke, () => { attachClickEvent(btnRevoke, () => {
new PopupConfirmAction('revoke-link', [{ new PopupPeer('revoke-link', {
langKey: 'RevokeButton', buttons: [{
callback: () => { langKey: 'RevokeButton',
const toggle = toggleDisability([btnRevoke], true); callback: () => {
const toggle = toggleDisability([btnRevoke], true);
appProfileManager.getChatInviteLink(-this.peerId, true).then(link => {
toggle(); appProfileManager.getChatInviteLink(-this.peerId, true).then(link => {
linkRow.title.innerHTML = link; toggle();
//revoked = true; linkRow.title.innerHTML = link;
//onChange(); //revoked = true;
}); //onChange();
} });
}], { }
title: 'RevokeLink', }],
text: 'RevokeAlert' titleLangKey: 'RevokeLink',
descriptionLangKey: 'RevokeAlert'
}).show(); }).show();
}, {listenerSetter: this.listenerSetter}); }, {listenerSetter: this.listenerSetter});

53
src/lang.ts

@ -81,6 +81,12 @@ const lang = {
"one_value": "Remove %d Sticker", "one_value": "Remove %d Sticker",
"other_value": "Remove %d Stickers" "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 // * android
"ActionCreateChannel": "Channel created", "ActionCreateChannel": "Channel created",
@ -153,6 +159,10 @@ const lang = {
"one_value": "%1$d member", "one_value": "%1$d member",
"other_value": "%1$d members" "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", "UsernameHelpLink": "This link opens a chat with you:\n%1$s",
"NewChannel": "New Channel", "NewChannel": "New Channel",
"NewGroup": "New Group", "NewGroup": "New Group",
@ -214,6 +224,13 @@ const lang = {
"TerminateAllSessions": "Terminate All Other Sessions", "TerminateAllSessions": "Terminate All Other Sessions",
"TerminateSessionText": "Are you sure you want to terminate this session?", "TerminateSessionText": "Are you sure you want to terminate this session?",
"OtherSessions": "Active sessions", "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", "AreYouSureSessionTitle": "Terminate session",
"AreYouSureSessionsTitle": "Terminate sessions", "AreYouSureSessionsTitle": "Terminate sessions",
"AreYouSureSessions": "Are you sure you want to terminate all other sessions?", "AreYouSureSessions": "Are you sure you want to terminate all other sessions?",
@ -314,6 +331,27 @@ const lang = {
"other_value": "%1$d subscribers" "other_value": "%1$d subscribers"
}, },
"SearchGifsTitle": "Search GIFs", "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 // * macos
"AccountSettings.Filters": "Chat Folders", "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.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.", "Channel.UsernameAboutGroup": "People can share this link with others and find your group using Telegram search.",
"Chat.CopySelectedText": "Copy Selected Text", "Chat.CopySelectedText": "Copy Selected Text",
"Chat.Confirm.Unpin": "Would you like to unpin this message?",
"Chat.Date.ScheduledFor": "Scheduled for %@", "Chat.Date.ScheduledFor": "Scheduled for %@",
//"Chat.Date.ScheduledUntilOnline": "Scheduled until online", //"Chat.Date.ScheduledUntilOnline": "Scheduled until online",
"Chat.Date.ScheduledForToday": "Scheduled for today", "Chat.Date.ScheduledForToday": "Scheduled for today",
@ -368,10 +407,18 @@ const lang = {
"Chat.Send.WithoutSound": "Send Without Sound", "Chat.Send.WithoutSound": "Send Without Sound",
"Chat.Send.SetReminder": "Set a Reminder", "Chat.Send.SetReminder": "Set a Reminder",
"Chat.Send.ScheduledMessage": "Schedule Message", "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.Mute": "Mute",
"ChatList.Context.Unmute": "Unmute", "ChatList.Context.Unmute": "Unmute",
"ChatList.Context.Pin": "Pin", "ChatList.Context.Pin": "Pin",
"ChatList.Context.Unpin": "Unpin", "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.incoming": "Incoming Call (%@)",
"ChatList.Service.Call.outgoing": "Outgoing Call (%@)", "ChatList.Service.Call.outgoing": "Outgoing Call (%@)",
"ChatList.Service.Call.Cancelled": "Cancelled Call", "ChatList.Service.Call.Cancelled": "Cancelled Call",
@ -458,6 +505,7 @@ const lang = {
"PeerInfo.SignMessages": "Sign Messages", "PeerInfo.SignMessages": "Sign Messages",
"PeerInfo.SharedMedia": "Shared Media", "PeerInfo.SharedMedia": "Shared Media",
"PeerInfo.Subscribers": "Subscribers", "PeerInfo.Subscribers": "Subscribers",
"PeerInfo.DeleteContact": "Delete Contact",
"PollResults.Title.Poll": "Poll Results", "PollResults.Title.Poll": "Poll Results",
"PollResults.Title.Quiz": "Quiz Results", "PollResults.Title.Quiz": "Quiz Results",
"PollResults.LoadMore": { "PollResults.LoadMore": {
@ -512,7 +560,10 @@ const lang = {
"NewPoll.OptionsAddOption": "Add an Option", "NewPoll.OptionsAddOption": "Add an Option",
"NewPoll.MultipleChoice": "Multiple Answers", "NewPoll.MultipleChoice": "Multiple Answers",
"NewPoll.Quiz": "Quiz Mode", "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; export default lang;

1064
src/layer.d.ts vendored

File diff suppressed because it is too large Load Diff

33
src/lib/appManagers/appChatsManager.ts

@ -62,16 +62,17 @@ export class AppChatsManager {
case 'updateUserTyping': case 'updateUserTyping':
case 'updateChatUserTyping': { 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; 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] = []); 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) { if(!typing) {
typing = { typing = {
userId: update.user_id userId: fromId
}; };
typings.push(typing); typings.push(typing);
@ -81,7 +82,7 @@ export class AppChatsManager {
typing.action = update.action; typing.action = update.action;
if(!appUsersManager.hasUser(update.user_id)) { if(!appUsersManager.hasUser(fromId)) {
if(update._ === 'updateChatUserTyping') { if(update._ === 'updateChatUserTyping') {
if(update.chat_id && appChatsManager.hasChat(update.chat_id) && !appChatsManager.isChannel(update.chat_id)) { if(update.chat_id && appChatsManager.hasChat(update.chat_id) && !appChatsManager.isChannel(update.chat_id)) {
appProfileManager.getChatFull(update.chat_id); appProfileManager.getChatFull(update.chat_id);
@ -91,13 +92,13 @@ export class AppChatsManager {
//return; //return;
} }
appUsersManager.forceUserOnline(update.user_id); appUsersManager.forceUserOnline(fromId);
if(typing.timeout !== undefined) clearTimeout(typing.timeout); if(typing.timeout !== undefined) clearTimeout(typing.timeout);
typing.timeout = window.setTimeout(() => { typing.timeout = window.setTimeout(() => {
delete typing.timeout; delete typing.timeout;
typings.findAndSplice(t => t.userId === update.user_id); typings.findAndSplice(t => t.userId === fromId);
rootScope.broadcast('peer_typings', {peerId, typings}); rootScope.broadcast('peer_typings', {peerId, typings});
@ -571,22 +572,36 @@ export class AppChatsManager {
}).then(this.onChatUpdated.bind(this, id)); }).then(this.onChatUpdated.bind(this, id));
} }
public leaveChat(id: number) { public leaveChat(id: number, flushHistory = true) {
return this.deleteChatUser(id, appUsersManager.getSelf().id).then(() => { let promise: Promise<any> = this.deleteChatUser(id, appUsersManager.getSelf().id)
if(flushHistory) promise = promise.then(() => {
return appMessagesManager.flushHistory(-id); return appMessagesManager.flushHistory(-id);
}); });
return promise;;
} }
public leave(id: number) { public leave(id: number) {
return this.isChannel(id) ? this.leaveChannel(id) : this.leaveChat(id); 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) { public deleteChannel(id: number) {
return apiManager.invokeApi('channels.deleteChannel', { return apiManager.invokeApi('channels.deleteChannel', {
channel: this.getChannelInput(id) channel: this.getChannelInput(id)
}).then(this.onChatUpdated.bind(this, 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> { public migrateChat(id: number): Promise<number> {
const chat: Chat = this.getChat(id); const chat: Chat = this.getChat(id);
if(chat._ === 'channel') return Promise.resolve(chat.id); if(chat._ === 'channel') return Promise.resolve(chat.id);

23
src/lib/appManagers/appImManager.ts

@ -39,6 +39,7 @@ import appNavigationController from '../../components/appNavigationController';
import appNotificationsManager from './appNotificationsManager'; import appNotificationsManager from './appNotificationsManager';
import AppPrivateSearchTab from '../../components/sidebarRight/tabs/search'; import AppPrivateSearchTab from '../../components/sidebarRight/tabs/search';
import { i18n } from '../langPack'; import { i18n } from '../langPack';
import { SendMessageAction } from '../../layer';
//console.log('appImManager included33!'); //console.log('appImManager included33!');
@ -742,6 +743,26 @@ export class AppImManager {
this.setInnerPeer(peerId, undefined, 'scheduled'); 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) { public async getPeerStatus(peerId: number) {
let subtitle: HTMLElement; let subtitle: HTMLElement;
if(!peerId) return ''; if(!peerId) return '';
@ -777,7 +798,7 @@ export class AppImManager {
if(typings && typings.length) { if(typings && typings.length) {
const span = document.createElement('span'); const span = document.createElement('span');
span.classList.add('online'); 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; return span;
} else if(user.status?._ === 'userStatusOnline') { } else if(user.status?._ === 'userStatusOnline') {
const span = document.createElement('span'); const span = document.createElement('span');

7
src/lib/appManagers/appMessagesManager.ts

@ -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', { return apiManager.invokeApi('messages.deleteHistory', {
just_clear: justClear, just_clear: justClear,
revoke: revoke,
peer: inputPeer, peer: inputPeer,
max_id: 0 max_id: 0
}).then((affectedHistory) => { }).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)) { if(appPeersManager.isChannel(peerId)) {
const promise = this.getHistory(peerId, 0, 1); 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.historiesStorage[peerId];
delete this.messagesStorageByPeerId[peerId]; delete this.messagesStorageByPeerId[peerId];

14
src/lib/appManagers/appPeersManager.ts

@ -1,6 +1,7 @@
import { MOUNT_CLASS_TO } from "../../config/debug"; import { MOUNT_CLASS_TO } from "../../config/debug";
import { isObject } from "../../helpers/object"; import { isObject } from "../../helpers/object";
import { DialogPeer, InputDialogPeer, InputNotifyPeer, InputPeer, Peer, Update } from "../../layer"; import { DialogPeer, InputDialogPeer, InputNotifyPeer, InputPeer, Peer, Update } from "../../layer";
import { LangPackKey } from "../langPack";
import { RichTextProcessor } from "../richtextprocessor"; import { RichTextProcessor } from "../richtextprocessor";
import rootScope from "../rootScope"; import rootScope from "../rootScope";
import appChatsManager from "./appChatsManager"; import appChatsManager from "./appChatsManager";
@ -275,14 +276,19 @@ export class AppPeersManager {
} }
} }
public getDeleteButtonText(peerId: number) { public getDeleteButtonText(peerId: number): LangPackKey {
switch(this.getDialogType(peerId)) { switch(this.getDialogType(peerId)) {
case 'megagroup':
case 'channel': case 'channel':
return 'Leave'; return 'ChatList.Context.LeaveChannel';
case 'megagroup':
return 'ChatList.Context.LeaveGroup';
case 'group':
return 'ChatList.Context.DeleteAndExit';
default: default:
return 'Delete'; return 'ChatList.Context.DeleteChat';
} }
} }
} }

2
src/lib/appManagers/appStickersManager.ts

@ -123,7 +123,7 @@ export class AppStickersManager {
} }
public getStickerSetThumbDownloadOptions(stickerSet: StickerSet.stickerSet) { 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 dcId = stickerSet.thumb_dc_id;
const isAnimated = stickerSet.pFlags?.animated; const isAnimated = stickerSet.pFlags?.animated;

4
src/lib/mtproto/schema.ts

File diff suppressed because one or more lines are too long

6
src/lib/mtproto/tl_utils.ts

@ -276,7 +276,7 @@ class TLSerialization {
this.storeInt(methodData.id, methodName + '[id]'); 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} = {}; const flagsOffsets: {[paramName: string]: number} = {};
//console.log('storeMethod', len, methodData); //console.log('storeMethod', len, methodData);
for(const param of methodData.params) { for(const param of methodData.params) {
@ -287,7 +287,7 @@ class TLSerialization {
const fieldBit = condType[0].split('.'); const fieldBit = condType[0].split('.');
if(!(params[fieldBit[0]] & (1 << +fieldBit[1]))) { 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); //console.log('storeMethod autocompleting', methodName, param.name, params[param.name], type);
params[fieldBit[0]] |= 1 << +fieldBit[1]; params[fieldBit[0]] |= 1 << +fieldBit[1];
} else { } else {
@ -399,7 +399,7 @@ class TLSerialization {
//console.log('storeObject fieldBit', fieldBit, obj[fieldBit[0]]); //console.log('storeObject fieldBit', fieldBit, obj[fieldBit[0]]);
if(!(obj[fieldBit[0]] & (1 << +fieldBit[1]))) { 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); //console.log('storeObject autocompleting', param.name, obj[param.name], type);
obj[fieldBit[0]] |= 1 << +fieldBit[1]; obj[fieldBit[0]] |= 1 << +fieldBit[1];
} else { } else {

15
src/scripts/generate_mtproto_types.js

@ -63,15 +63,16 @@ function camelizeName(string, camelizeFirstLetterIfFound, camelizeFirstLetterIfN
}); });
} }
/** @type {(type: string) => any} */ /** @type {(type: string, parseBooleanFlags: boolean) => any} */
const processParamType = (type) => { const processParamType = (type, parseBooleanFlags) => {
const isAdditional = type.indexOf('flags.-1?') === 0; const isAdditional = type.indexOf('flags.-1?') === 0;
if(type.includes('?')) { const isFlag = type.includes('?');
if(isFlag) {
type = type.split('?')[1]; type = type.split('?')[1];
} }
if(type.includes('Vector')) { if(type.includes('Vector')) {
return `Array<${processParamType(type.slice(7, -1))}>`; return `Array<${processParamType(type.slice(7, -1), parseBooleanFlags)}>`;
} }
switch(type) { switch(type) {
@ -80,7 +81,7 @@ const processParamType = (type) => {
return 'number'; return 'number';
case 'true': case 'true':
return 'true'; return parseBooleanFlags ? 'true' : 'boolean';
case 'Bool': case 'Bool':
return 'boolean'; return 'boolean';
@ -121,7 +122,7 @@ const processParams = (params, object = {}, parseBooleanFlags = true) => {
type = replace[name]; type = replace[name];
} }
const processed = processParamType(type); const processed = processParamType(type, parseBooleanFlags);
if(type.includes('?true') && parseBooleanFlags) { if(type.includes('?true') && parseBooleanFlags) {
if(!object.pFlags) object.pFlags = {}; if(!object.pFlags) object.pFlags = {};
object.pFlags[name] = processed; object.pFlags[name] = processed;
@ -236,7 +237,7 @@ mtproto.methods.forEach((_method) => {
const camelizedMethod = camelizeName(method, true, true); 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`; let str = `export type ${camelizedMethod} = {\n`;

2
src/scripts/in/schema.json

File diff suppressed because one or more lines are too long

2
src/scripts/out/schema.json

File diff suppressed because one or more lines are too long

107
src/scss/partials/_peerTyping.scss

@ -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;
}
}

4
src/scss/partials/popups/_datePicker.scss

@ -6,8 +6,8 @@
&-container { &-container {
// max-width: 300px; // max-width: 300px;
// max-height: 424px; // max-height: 424px;
min-width: 300px; /* min-width: 300px;
width: 300px; width: 300px; */
padding: 12px 14px; padding: 12px 14px;
@media (min-height: 470px) { @media (min-height: 470px) {

15
src/scss/partials/popups/_peer.scss

@ -11,6 +11,7 @@
&-container { &-container {
padding: 1rem 1.5rem .75rem 1rem; padding: 1rem 1.5rem .75rem 1rem;
max-width: unquote('min(400px, 100%)');
} }
&-title { &-title {
@ -46,10 +47,16 @@
} }
} }
} }
}
.popup-peer, .popup-confirm-action { .checkbox-field {
.popup-container { display: flex;
max-width: unquote('min(400px, 100%)'); align-items: center;
height: 3.5rem;
padding: 0 1.1875rem;
margin: 0;
.checkbox-box {
left: auto;
}
} }
} }

6
src/scss/partials/popups/_popup.scss

@ -133,10 +133,4 @@
} }
} }
} }
&.popup-delete-chat {
.popup-container {
max-width: 328px;
}
}
} }

1
src/scss/style.scss

@ -137,6 +137,7 @@ $chat-padding-handhelds: .5rem;
@import "partials/document"; @import "partials/document";
@import "partials/audio"; @import "partials/audio";
@import "partials/quizHint"; @import "partials/quizHint";
@import "partials/peerTyping";
@import "partials/popups/popup"; @import "partials/popups/popup";
@import "partials/popups/editAvatar"; @import "partials/popups/editAvatar";

Loading…
Cancel
Save