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) => { @@ -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) => {
@ -54,4 +53,4 @@ const ButtonMenu = (buttons: ButtonMenuItemOptions[], listenerSetter?: ListenerS @@ -54,4 +53,4 @@ const ButtonMenu = (buttons: ButtonMenuItemOptions[], listenerSetter?: ListenerS
return el;
};
export default ButtonMenu;
export default ButtonMenu;

4
src/components/chat/bubbles.ts

@ -811,7 +811,7 @@ export default class ChatBubbles { @@ -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 { @@ -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;

24
src/components/checkboxField.ts

@ -3,21 +3,23 @@ import { getDeepProperty } from "../helpers/object"; @@ -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 { @@ -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');
}

5
src/components/dialogsContextMenu.ts

@ -6,6 +6,7 @@ import { findUpTag } from "../helpers/dom"; @@ -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 { @@ -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);
@ -159,4 +160,4 @@ export default class DialogsContextMenu { @@ -159,4 +160,4 @@ export default class DialogsContextMenu {
this.selectedId = this.dialog = this.filterId = undefined;
});
};
}
}

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

@ -200,7 +200,7 @@ export default class StickersTab implements EmoticonsTab { @@ -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);

3
src/components/popups/avatar.ts

@ -2,6 +2,7 @@ import appDownloadManager from "../../lib/appManagers/appDownloadManager"; @@ -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 { @@ -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');

19
src/components/popups/confirmAction.ts

@ -1,19 +0,0 @@ @@ -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 @@ @@ -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 { @@ -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));
}
}

136
src/components/popups/deleteDialog.ts

@ -1,28 +1,51 @@ @@ -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;
/* const callbackFlush = (checked: PopupPeerButtonCallbackCheckboxes) => {
const promise = appMessagesManager.flushHistory(peerId, checkboxes ? !checked[checkboxes[0].text] : undefined);
onSelect && onSelect(promise);
}; */
let callbackFlush = (justClear?: true) => {
appMessagesManager.flushHistory(peerId, justClear);
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 { @@ -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();
}
}
}

77
src/components/popups/deleteMessages.ts

@ -1,14 +1,18 @@ @@ -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 { @@ -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()
}];
if(peerId > 0) {
buttons.push({
text: 'DELETE FOR ME AND ' + firstName,
langKey: 'DeleteMessagesOptionAlso',
langArgs: [peerTitleElement],
isDanger: true,
callback: () => callback(true)
});
@ -56,47 +63,39 @@ export default class PopupDeleteMessages { @@ -56,47 +63,39 @@ 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
});
popup.show();
}
}
}

4
src/components/popups/index.ts

@ -93,13 +93,13 @@ export default class PopupElement { @@ -93,13 +93,13 @@ 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', () => {

75
src/components/popups/peer.ts

@ -1,32 +1,67 @@ @@ -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});
let avatarEl = new AvatarElement();
avatarEl.setAttribute('dialog', '1');
avatarEl.setAttribute('peer', '' + options.peerId);
avatarEl.classList.add('avatar-32');
if(options.descriptionLangKey) this.title.append(i18n(options.titleLangKey));
constructor(private className: string, options: PopupPeerOptions = {}) {
super('popup-peer' + (className ? ' ' + className : ''), addCancelButton(options.buttons), {overlayClosable: true, ...options});
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, 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);
}
}

67
src/components/popups/unpinMessage.ts

@ -1,12 +1,15 @@ @@ -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,77 +32,79 @@ export default class PopupPinMessage { @@ -29,77 +32,79 @@ 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
});
popup.show();
}
}
}

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

@ -6,12 +6,12 @@ import { SliderSuperTab } from "../../../slider"; @@ -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 { @@ -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();

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

@ -4,7 +4,7 @@ import { AccountPassword } from "../../../../layer"; @@ -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 { @@ -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();

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

@ -7,11 +7,11 @@ import { formatDateAccordingToToday } from "../../../helpers/date"; @@ -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 { @@ -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 { @@ -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();
};

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

@ -126,7 +126,7 @@ export default class AppEditContactTab extends SliderSuperTab { @@ -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', {

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

@ -15,6 +15,7 @@ import AppGroupTypeTab from "./groupType"; @@ -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 { @@ -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);

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

@ -6,7 +6,6 @@ import appChatsManager from "../../../lib/appManagers/appChatsManager"; @@ -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"; @@ -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 { @@ -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);
appProfileManager.getChatInviteLink(-this.peerId, true).then(link => {
toggle();
linkRow.title.innerHTML = link;
//revoked = true;
//onChange();
});
}
}], {
title: 'RevokeLink',
text: 'RevokeAlert'
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();
});
}
}],
titleLangKey: 'RevokeLink',
descriptionLangKey: 'RevokeAlert'
}).show();
}, {listenerSetter: this.listenerSetter});

53
src/lang.ts

@ -81,6 +81,12 @@ const lang = { @@ -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 = { @@ -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 = { @@ -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 = { @@ -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 = { @@ -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 = { @@ -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 = { @@ -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 = { @@ -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

File diff suppressed because it is too large Load Diff

33
src/lib/appManagers/appChatsManager.ts

@ -62,16 +62,17 @@ export class AppChatsManager { @@ -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 { @@ -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 { @@ -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 { @@ -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);

23
src/lib/appManagers/appImManager.ts

@ -39,6 +39,7 @@ import appNavigationController from '../../components/appNavigationController'; @@ -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 { @@ -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 { @@ -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');

7
src/lib/appManagers/appMessagesManager.ts

@ -1991,9 +1991,10 @@ export class AppMessagesManager { @@ -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 { @@ -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 { @@ -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];

14
src/lib/appManagers/appPeersManager.ts

@ -1,6 +1,7 @@ @@ -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 { @@ -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';
}
}
}

2
src/lib/appManagers/appStickersManager.ts

@ -123,7 +123,7 @@ export class AppStickersManager { @@ -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;

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 { @@ -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 { @@ -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 { @@ -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 {

15
src/scripts/generate_mtproto_types.js

@ -63,15 +63,16 @@ function camelizeName(string, camelizeFirstLetterIfFound, camelizeFirstLetterIfN @@ -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) => { @@ -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) => { @@ -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) => { @@ -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`;

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 @@ @@ -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 @@ @@ -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) {

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

@ -11,6 +11,7 @@ @@ -11,6 +11,7 @@
&-container {
padding: 1rem 1.5rem .75rem 1rem;
max-width: unquote('min(400px, 100%)');
}
&-title {
@ -46,10 +47,16 @@ @@ -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;
}
}
}

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

@ -133,10 +133,4 @@ @@ -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; @@ -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…
Cancel
Save