Browse Source

Merge branch 'master' into premium

master
Eduard Kuzmenko 2 years ago
parent
commit
3e30204816
  1. 4
      .env
  2. 32
      src/components/appMediaPlaybackController.ts
  3. 16
      src/components/appMediaViewer.ts
  4. 45
      src/components/appSearchSuper..ts
  5. 4
      src/components/appSelectPeers.ts
  6. 2
      src/components/call/index.ts
  7. 2
      src/components/call/videoCanvasBlur.ts
  8. 57
      src/components/chat/bubbles.ts
  9. 10
      src/components/chat/chat.ts
  10. 4
      src/components/chat/gradientRenderer.ts
  11. 4
      src/components/chat/inlineHelper.ts
  12. 26
      src/components/chat/input.ts
  13. 5
      src/components/chat/replyKeyboard.ts
  14. 6
      src/components/chat/selection.ts
  15. 33
      src/components/checkboxField.ts
  16. 17
      src/components/emoticonsDropdown/index.ts
  17. 3
      src/components/emoticonsDropdown/tabs/emoji.ts
  18. 3
      src/components/emoticonsDropdown/tabs/gifs.ts
  19. 445
      src/components/emoticonsDropdown/tabs/stickers.ts
  20. 2
      src/components/gifsMasonry.ts
  21. 11
      src/components/horizontalMenu.ts
  22. 4
      src/components/lazyLoadQueue.ts
  23. 9
      src/components/lazyLoadQueueRepeat.ts
  24. 5
      src/components/lazyLoadQueueRepeat2.ts
  25. 16
      src/components/peerProfile.ts
  26. 1
      src/components/peerProfileAvatars.ts
  27. 4
      src/components/poll.ts
  28. 142
      src/components/popups/payment.ts
  29. 1
      src/components/popups/paymentCard.ts
  30. 2
      src/components/popups/reactedList.ts
  31. 18
      src/components/row.ts
  32. 16
      src/components/scrollable.ts
  33. 6
      src/components/sidebarLeft/index.ts
  34. 2
      src/components/sidebarLeft/tabs/autoDownload/file.ts
  35. 17
      src/components/sidebarLeft/tabs/autoDownload/photo.ts
  36. 2
      src/components/sidebarLeft/tabs/autoDownload/video.ts
  37. 15
      src/components/sidebarLeft/tabs/dataAndStorage.ts
  38. 19
      src/components/sidebarLeft/tabs/editFolder.ts
  39. 21
      src/components/sidebarLeft/tabs/generalSettings.ts
  40. 7
      src/components/sidebarLeft/tabs/includedChats.ts
  41. 21
      src/components/sidebarLeft/tabs/language.ts
  42. 4
      src/components/sidebarLeft/tabs/newChannel.ts
  43. 6
      src/components/sidebarLeft/tabs/newGroup.ts
  44. 14
      src/components/sidebarLeft/tabs/notifications.ts
  45. 27
      src/components/sidebarLeft/tabs/privacyAndSecurity.ts
  46. 25
      src/components/sidebarLeft/tabs/settings.ts
  47. 16
      src/components/sidebarRight/index.ts
  48. 6
      src/components/sidebarRight/tabs/chatReactions.ts
  49. 3
      src/components/sidebarRight/tabs/chatType.ts
  50. 9
      src/components/sidebarRight/tabs/editChat.ts
  51. 3
      src/components/sidebarRight/tabs/editContact.ts
  52. 3
      src/components/sidebarRight/tabs/groupPermissions.ts
  53. 11
      src/components/sidebarRight/tabs/sharedMedia.ts
  54. 3
      src/components/singleTransition.ts
  55. 2
      src/components/slider.ts
  56. 6
      src/components/sliderTab.ts
  57. 28
      src/components/transition.ts
  58. 19
      src/components/visibilityIntersector.ts
  59. 5
      src/components/wrappers/messageActionTextNewUnsafe.ts
  60. 9
      src/components/wrappers/stickerSetThumb.ts
  61. 13
      src/components/wrappers/video.ts
  62. 5
      src/helpers/bigInt/bigIntConstants.ts
  63. 9
      src/helpers/bigInt/bigIntConversion.ts
  64. 2
      src/helpers/canvas/getTextWidth.ts
  65. 11
      src/helpers/cards/cardBrands.ts
  66. 7
      src/helpers/cards/validateCard.ts
  67. 7
      src/helpers/dom/renderImageWithFadeIn.ts
  68. 8
      src/helpers/dropdownHover.ts
  69. 75
      src/helpers/paymentsWrapCurrencyAmount.ts
  70. 8
      src/helpers/searchListLoader.ts
  71. 4
      src/index.hbs
  72. 2
      src/lang.ts
  73. 1
      src/lib/appManagers/appDialogsManager.ts
  74. 80
      src/lib/appManagers/appImManager.ts
  75. 7
      src/lib/appManagers/appMessagesManager.ts
  76. 22
      src/lib/appManagers/appStickersManager.ts
  77. 3
      src/lib/calls/callsController.ts
  78. 6
      src/lib/crypto/computeDhKey.ts
  79. 48
      src/lib/langPack.ts
  80. 19
      src/lib/mediaPlayer.ts
  81. 16
      src/lib/mtproto/tl_utils.ts
  82. 5
      src/lib/richTextProcessor/wrapRichText.ts
  83. 4
      src/lib/rlottie/queryableWorker.ts
  84. 4
      src/lib/rootScope.ts
  85. 83
      src/scripts/out/langPack.strings
  86. 4
      src/scss/components/_global.scss
  87. 32
      src/scss/fonts/_roboto.scss
  88. 3
      src/scss/partials/_chatBubble.scss
  89. 2
      src/scss/partials/_ckin.scss
  90. 230
      src/scss/partials/_emojiDropdown.scss
  91. 1
      src/scss/partials/popups/_call.scss
  92. 1
      src/scss/partials/popups/_forward.scss
  93. 1
      src/scss/partials/popups/_groupCall.scss
  94. 3
      src/scss/partials/popups/_payment.scss
  95. 2
      src/scss/partials/popups/_popup.scss
  96. 20
      src/scss/style.scss
  97. 80
      webpack.common.js

4
.env

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
API_ID=1025907
API_HASH=452b0359b988148995f22ff0f4229750
VERSION=1.5.0
VERSION_FULL=1.5.0 (196)
BUILD=196
VERSION_FULL=1.5.0 (215)
BUILD=215

32
src/components/appMediaPlaybackController.ts

@ -584,23 +584,29 @@ export class AppMediaPlaybackController extends EventListenerBase<{ @@ -584,23 +584,29 @@ export class AppMediaPlaybackController extends EventListenerBase<{
const listLoader = this.listLoader;
const current = listLoader.getCurrent();
if(!current || !verify(current)) {
const previous = listLoader.getPrevious();
let idx = previous.findIndex(verify);
let jumpLength: number;
if(idx !== -1) {
jumpLength = -(previous.length - idx);
} else {
idx = listLoader.getNext().findIndex(verify);
for(const withOtherSide of [false, true]) {
const previous = listLoader.getPrevious(withOtherSide);
let idx = previous.findIndex(verify);
if(idx !== -1) {
jumpLength = idx + 1;
jumpLength = -(previous.length - idx);
} else {
const next = listLoader.getNext(withOtherSide);
idx = next.findIndex(verify);
if(idx !== -1) {
jumpLength = idx + 1;
}
}
}
if(idx !== -1) {
if(jumpLength) {
this.go(jumpLength, false);
if(jumpLength !== undefined) {
break;
}
}
if(jumpLength) {
this.go(jumpLength, false);
} else {
this.setTargets({peerId, mid});
}
@ -645,7 +651,7 @@ export class AppMediaPlaybackController extends EventListenerBase<{ @@ -645,7 +651,7 @@ export class AppMediaPlaybackController extends EventListenerBase<{
const listLoader = this.listLoader;
if(this.lockedSwitchers ||
(!this.round && listLoader.current && !listLoader.next.length) ||
!listLoader.getNext().length ||
!listLoader.getNext(true).length ||
!this.next()) {
this.stop();
this.dispatchEvent('stop');

16
src/components/appMediaViewer.ts

@ -7,6 +7,8 @@ @@ -7,6 +7,8 @@
import MEDIA_MIME_TYPES_SUPPORTED from '../environment/mediaMimeTypesSupport';
import cancelEvent from '../helpers/dom/cancelEvent';
import {attachClickEvent, detachClickEvent} from '../helpers/dom/clickEvent';
import findUpClassName from '../helpers/dom/findUpClassName';
import findUpTag from '../helpers/dom/findUpTag';
import setInnerHTML from '../helpers/dom/setInnerHTML';
import mediaSizes from '../helpers/mediaSizes';
import SearchListLoader from '../helpers/searchListLoader';
@ -68,7 +70,7 @@ export default class AppMediaViewer extends AppMediaViewerBase<'caption', 'delet @@ -68,7 +70,7 @@ export default class AppMediaViewer extends AppMediaViewerBase<'caption', 'delet
this.content.main.prepend(stub); */
this.content.caption = document.createElement('div');
this.content.caption.classList.add(MEDIA_VIEWER_CLASSNAME + '-caption'/* , 'media-viewer-stub' */);
this.content.caption.classList.add(MEDIA_VIEWER_CLASSNAME + '-caption', 'message'/* , 'media-viewer-stub' */);
let captionTimeout: number;
const setCaptionTimeout = () => {
@ -129,8 +131,10 @@ export default class AppMediaViewer extends AppMediaViewerBase<'caption', 'delet @@ -129,8 +131,10 @@ export default class AppMediaViewer extends AppMediaViewerBase<'caption', 'delet
attachClickEvent(this.author.container, this.onAuthorClick);
const onCaptionClick = (e: MouseEvent) => {
if(e.target instanceof HTMLAnchorElement) { // close viewer if it's t.me/ redirect
const onclick = (e.target as HTMLElement).getAttribute('onclick');
const a = findUpTag(e.target, 'A');
const spoiler = findUpClassName(e.target, 'spoiler');
if(a instanceof HTMLAnchorElement && (!spoiler || this.content.caption.classList.contains('is-spoiler-visible'))) { // close viewer if it's t.me/ redirect
const onclick = a.getAttribute('onclick');
if(!onclick || onclick.includes('showMaskedAlert')) {
return;
}
@ -138,15 +142,15 @@ export default class AppMediaViewer extends AppMediaViewerBase<'caption', 'delet @@ -138,15 +142,15 @@ export default class AppMediaViewer extends AppMediaViewerBase<'caption', 'delet
cancelEvent(e);
this.close().then(() => {
detachClickEvent(this.content.caption, onCaptionClick, {capture: true});
(e.target as HTMLAnchorElement).click();
this.content.caption.removeEventListener('click', onCaptionClick, {capture: true});
a.click();
});
return false;
}
};
attachClickEvent(this.content.caption, onCaptionClick, {capture: true});
this.content.caption.addEventListener('click', onCaptionClick, {capture: true});
}
/* public close(e?: MouseEvent) {

45
src/components/appSearchSuper..ts

@ -69,6 +69,8 @@ import {attachContextMenuListener} from '../helpers/dom/attachContextMenuListene @@ -69,6 +69,8 @@ import {attachContextMenuListener} from '../helpers/dom/attachContextMenuListene
import contextMenuController from '../helpers/contextMenuController';
import positionMenu from '../helpers/positionMenu';
import apiManagerProxy from '../lib/mtproto/mtprotoworker';
import ListenerSetter from '../helpers/listenerSetter';
import SwipeHandler from './swipeHandler';
// const testScroll = false;
@ -107,7 +109,8 @@ class SearchContextMenu { @@ -107,7 +109,8 @@ class SearchContextMenu {
constructor(
private attachTo: HTMLElement,
private searchSuper: AppSearchSuper
private searchSuper: AppSearchSuper,
private listenerSetter: ListenerSetter
) {
this.managers = searchSuper.managers;
@ -162,7 +165,7 @@ class SearchContextMenu { @@ -162,7 +165,7 @@ class SearchContextMenu {
if(IS_TOUCH_SUPPORTED) {
} else {
attachContextMenuListener(attachTo, onContextMenu as any);
attachContextMenuListener(attachTo, onContextMenu as any, listenerSetter);
}
}
@ -323,14 +326,18 @@ export default class AppSearchSuper { @@ -323,14 +326,18 @@ export default class AppSearchSuper {
public managers: AppManagers;
private loadFirstTimePromise: Promise<void>;
private listenerSetter: ListenerSetter;
private swipeHandler: SwipeHandler;
constructor(options: Pick<AppSearchSuper, 'mediaTabs' | 'scrollable' | 'searchGroups' | 'asChatList' | 'groupByMonth' | 'hideEmptyTabs' | 'onChangeTab' | 'showSender' | 'managers'>) {
safeAssign(this, options);
this.container = document.createElement('div');
this.container.classList.add('search-super');
this.searchContextMenu = new SearchContextMenu(this.container, this);
this.selection = new SearchSelection(this, this.managers);
this.listenerSetter = new ListenerSetter();
this.searchContextMenu = new SearchContextMenu(this.container, this, this.listenerSetter);
this.selection = new SearchSelection(this, this.managers, this.listenerSetter);
const navScrollableContainer = this.navScrollableContainer = document.createElement('div');
navScrollableContainer.classList.add('search-super-tabs-scrollable', 'menu-horizontal-scrollable', 'sticky');
@ -369,7 +376,7 @@ export default class AppSearchSuper { @@ -369,7 +376,7 @@ export default class AppSearchSuper {
let unlockScroll: ReturnType<typeof lockTouchScroll>;
if(IS_TOUCH_SUPPORTED) {
handleTabSwipe({
this.swipeHandler = handleTabSwipe({
element: this.tabsContainer,
onSwipe: (xDiff, yDiff, e) => {
const prevId = this.selectTab.prevId();
@ -521,14 +528,14 @@ export default class AppSearchSuper { @@ -521,14 +528,14 @@ export default class AppSearchSuper {
}
this.onTransitionEnd();
}, undefined, navScrollable);
}, undefined, navScrollable, this.listenerSetter);
attachClickEvent(this.tabsContainer, (e) => {
if(this.selection.isSelecting) {
cancelEvent(e);
this.selection.toggleByElement(findUpClassName(e.target, 'search-super-item'));
}
}, {capture: true, passive: false});
}, {capture: true, passive: false, listenerSetter: this.listenerSetter});
const onMediaClick = async(className: string, targetClassName: string, inputFilter: MyInputMessagesFilter, e: MouseEvent) => {
const target = findUpClassName(e.target as HTMLDivElement, className);
@ -560,8 +567,8 @@ export default class AppSearchSuper { @@ -560,8 +567,8 @@ export default class AppSearchSuper {
.openMedia(message, targets[idx].element, 0, false, targets.slice(0, idx), targets.slice(idx + 1));
};
attachClickEvent(this.tabs.inputMessagesFilterPhotoVideo, onMediaClick.bind(null, 'grid-item', 'grid-item', 'inputMessagesFilterPhotoVideo'));
attachClickEvent(this.tabs.inputMessagesFilterDocument, onMediaClick.bind(null, 'document-with-thumb', 'media-container', 'inputMessagesFilterDocument'));
attachClickEvent(this.tabs.inputMessagesFilterPhotoVideo, onMediaClick.bind(null, 'grid-item', 'grid-item', 'inputMessagesFilterPhotoVideo'), {listenerSetter: this.listenerSetter});
attachClickEvent(this.tabs.inputMessagesFilterDocument, onMediaClick.bind(null, 'document-with-thumb', 'media-container', 'inputMessagesFilterDocument'), {listenerSetter: this.listenerSetter});
/* attachClickEvent(this.tabs.inputMessagesFilterUrl, (e) => {
const target = e.target as HTMLElement;
@ -581,7 +588,7 @@ export default class AppSearchSuper { @@ -581,7 +588,7 @@ export default class AppSearchSuper {
this.lazyLoadQueue.lock();
}, () => {
this.lazyLoadQueue.unlockAndRefresh(); // ! maybe not so efficient
});
}, this.listenerSetter);
}
private onTransitionStart = () => {
@ -920,7 +927,7 @@ export default class AppSearchSuper { @@ -920,7 +927,7 @@ export default class AppSearchSuper {
element.dataset.peerId = '' + message.peerId;
monthContainer.items[method](element);
if(this.selection.isSelecting) {
if(this.selection?.isSelecting) {
this.selection.toggleElementCheckbox(element, true);
}
});
@ -1524,7 +1531,7 @@ export default class AppSearchSuper { @@ -1524,7 +1531,7 @@ export default class AppSearchSuper {
this.usedFromHistory[mediaTab.inputFilter] = -1;
});
if(this.selection.isSelecting) {
if(this.selection?.isSelecting) {
this.selection.cancelSelection();
}
@ -1641,4 +1648,18 @@ export default class AppSearchSuper { @@ -1641,4 +1648,18 @@ export default class AppSearchSuper {
this.cleanup();
}
public destroy() {
this.listenerSetter.removeAll();
this.scrollable.destroy();
this.swipeHandler?.removeListeners();
this.selection?.cleanup();
this.scrollStartCallback = undefined;
this.onChangeTab = undefined;
this.selectTab = undefined;
this.searchContextMenu = undefined;
this.swipeHandler = undefined;
this.selection = undefined;
}
}

4
src/components/appSelectPeers.ts

@ -21,7 +21,7 @@ import debounce from '../helpers/schedulers/debounce'; @@ -21,7 +21,7 @@ import debounce from '../helpers/schedulers/debounce';
import windowSize from '../helpers/windowSize';
import type {IsPeerType} from '../lib/appManagers/appPeersManager';
import {generateDelimiter, SettingSection} from './sidebarLeft';
import {attachClickEvent} from '../helpers/dom/clickEvent';
import {attachClickEvent, simulateClickEvent} from '../helpers/dom/clickEvent';
import filterUnique from '../helpers/array/filterUnique';
import indexOfAndSplice from '../helpers/array/indexOfAndSplice';
import safeAssign from '../helpers/object/safeAssign';
@ -187,7 +187,7 @@ export default class AppSelectPeers { @@ -187,7 +187,7 @@ export default class AppSelectPeers {
if(!li) {
this.remove(peerId.toPeerId());
} else {
li.click();
simulateClickEvent(li);
}
});

2
src/components/call/index.ts

@ -441,7 +441,7 @@ export default class PopupCall extends PopupElement { @@ -441,7 +441,7 @@ export default class PopupCall extends PopupElement {
if(!this.emojisSubtitle.textContent && connectionState < CALL_STATE.EXCHANGING_KEYS) {
Promise.resolve(instance.getEmojisFingerprint()).then((emojis) => {
this.emojisSubtitle.append(wrapEmojiText(emojis.join('')));
replaceContent(this.emojisSubtitle, wrapEmojiText(emojis.join('')));
});
}

2
src/components/call/videoCanvasBlur.ts

@ -13,7 +13,7 @@ export default function callVideoCanvasBlur(video: HTMLVideoElement) { @@ -13,7 +13,7 @@ export default function callVideoCanvasBlur(video: HTMLVideoElement) {
canvas.width = size;
canvas.height = size;
const ctx = canvas.getContext('2d');
const ctx = canvas.getContext('2d', {alpha: false});
ctx.filter = 'blur(2px)';
const renderFrame = () => {
ctx.drawImage(video, 0, 0, video.videoWidth, video.videoHeight, 0, 0, canvas.width, canvas.height);

57
src/components/chat/bubbles.ts

@ -1509,41 +1509,6 @@ export default class ChatBubbles { @@ -1509,41 +1509,6 @@ export default class ChatBubbles {
return;
}
const spoiler: HTMLElement = findUpClassName(target, 'spoiler');
if(spoiler) {
const messageDiv = findUpClassName(spoiler, 'message');
const className = 'is-spoiler-visible';
const isVisible = messageDiv.classList.contains(className);
if(!isVisible) {
cancelEvent(e);
}
const duration = 400 / 2;
const showDuration = 5000;
const useRafs = !isVisible ? 2 : 0;
if(useRafs) {
messageDiv.classList.add('will-change');
}
const spoilerTimeout = messageDiv.dataset.spoilerTimeout;
if(spoilerTimeout !== null) {
clearTimeout(+spoilerTimeout);
delete messageDiv.dataset.spoilerTimeout;
}
SetTransition(messageDiv, className, true, duration, () => {
messageDiv.dataset.spoilerTimeout = '' + window.setTimeout(() => {
SetTransition(messageDiv, className, false, duration, () => {
messageDiv.classList.remove('will-change');
delete messageDiv.dataset.spoilerTimeout;
});
}, showDuration);
}, useRafs);
return;
}
const reactionElement = findUpTag(target, 'REACTION-ELEMENT') as ReactionElement;
if(reactionElement) {
cancelEvent(e);
@ -1607,11 +1572,17 @@ export default class ChatBubbles { @@ -1607,11 +1572,17 @@ export default class ChatBubbles {
if(typeof(peerIdStr) === 'string' || savedFrom) {
if(savedFrom) {
const [peerId, mid] = savedFrom.split('_');
this.chat.appImManager.setInnerPeer({
peerId: peerId.toPeerId(),
lastMsgId: +mid
});
if(target.classList.contains('is-receipt-link')) {
const message = await this.managers.appMessagesManager.getMessageByPeer(peerId.toPeerId(), +mid);
if(message) {
new PopupPayment(message as Message.message, this.peerId, +bubble.dataset.mid);
}
} else {
this.chat.appImManager.setInnerPeer({
peerId: peerId.toPeerId(),
lastMsgId: +mid
});
}
} else {
const peerId = peerIdStr.toPeerId();
if(peerId !== NULL_PEER_ID) {
@ -2519,8 +2490,7 @@ export default class ChatBubbles { @@ -2519,8 +2490,7 @@ export default class ChatBubbles {
}
private destroyScrollable() {
this.scrollable.removeListeners();
this.scrollable.onScrolledTop = this.scrollable.onScrolledBottom = this.scrollable.onAdditionalScroll = null;
this.scrollable.destroy();
}
public destroy() {
@ -2566,6 +2536,7 @@ export default class ChatBubbles { @@ -2566,6 +2536,7 @@ export default class ChatBubbles {
// clear messages
if(bubblesToo) {
this.scrollable.container.textContent = '';
this.chatInner.textContent = '';
this.cleanupPlaceholders();
}
@ -2854,7 +2825,7 @@ export default class ChatBubbles { @@ -2854,7 +2825,7 @@ export default class ChatBubbles {
this.attachPlaceholderOnRender();
}
if(!isTarget && this.chat.type === 'chat') {
if(!isTarget && this.chat.type === 'chat' && this.chat.topbar.pinnedMessage) {
this.chat.topbar.pinnedMessage.setCorrectIndex(0);
}

10
src/components/chat/chat.ts

@ -439,6 +439,11 @@ export default class Chat extends EventListenerBase<{ @@ -439,6 +439,11 @@ export default class Chat extends EventListenerBase<{
this.inited = true;
}
// const appMediaViewer = (window as any).appMediaViewer as AppMediaViewerBase<any, any, any>;
// if(appMediaViewer) {
// appMediaViewer.close();
// }
const samePeer = this.peerId === peerId;
if(!samePeer) {
this.appImManager.dispatchEvent('peer_changing', this);
@ -453,6 +458,11 @@ export default class Chat extends EventListenerBase<{ @@ -453,6 +458,11 @@ export default class Chat extends EventListenerBase<{
this.cleanup(true);
this.bubbles.setPeer(false, peerId);
this.appImManager.dispatchEvent('peer_changed', peerId);
appSidebarRight.replaceSharedMediaTab();
this.destroySharedMediaTab();
this.sharedMediaTab = undefined;
return;
}

4
src/components/chat/gradientRenderer.ts

@ -283,11 +283,11 @@ export default class ChatBackgroundGradientRenderer { @@ -283,11 +283,11 @@ export default class ChatBackgroundGradientRenderer {
this._hc = document.createElement('canvas');
this._hc.width = this._width;
this._hc.height = this._height;
this._hctx = this._hc.getContext('2d');
this._hctx = this._hc.getContext('2d', {alpha: false});
}
this._canvas = el;
this._ctx = this._canvas.getContext('2d');
this._ctx = this._canvas.getContext('2d', {alpha: false});
this.update();
}

4
src/components/chat/inlineHelper.ts

@ -203,8 +203,8 @@ export default class InlineHelper extends AutocompleteHelper { @@ -203,8 +203,8 @@ export default class InlineHelper extends AutocompleteHelper {
} else if(media.type === 'sticker') {
container.classList.add('super-sticker');
this.superStickerRenderer.renderSticker(media, container, loadPromises);
if(media.sticker === 2) {
this.superStickerRenderer.observeAnimatedDiv(container);
if(media.animated) {
this.superStickerRenderer.observeAnimated(container);
}
}
} else if(media) {

26
src/components/chat/input.ts

@ -1633,7 +1633,7 @@ export default class ChatInput { @@ -1633,7 +1633,7 @@ export default class ChatInput {
executed.push(document.execCommand('styleWithCSS', false, 'true'));
if(type === 'monospace' || type === 'spoiler') {
const checkType = (type: MarkdownType) => {
let haveThisType = false;
// executed.push(document.execCommand('styleWithCSS', false, 'true'));
@ -1648,14 +1648,30 @@ export default class ChatInput { @@ -1648,14 +1648,30 @@ export default class ChatInput {
}
}
return haveThisType;
};
// * monospace can't be combined with different types
if(type === 'monospace' || type === 'spoiler') {
// executed.push(document.execCommand('styleWithCSS', false, 'true'));
const haveThisType = checkType(type);
// executed.push(document.execCommand('removeFormat', false, null));
if(haveThisType) {
executed.push(this.resetCurrentFormatting());
executed.push(this.resetCurrentFontFormatting());
} else {
if(type === 'monospace' || checkType('monospace')) {
executed.push(this.resetCurrentFormatting());
}
executed.push(typeof(command) === 'function' ? command() : document.execCommand(command, false, null));
}
} else {
if(checkType('monospace')) {
executed.push(this.resetCurrentFormatting());
}
executed.push(typeof(command) === 'function' ? command() : document.execCommand(command, false, null));
}
@ -1671,6 +1687,10 @@ export default class ChatInput { @@ -1671,6 +1687,10 @@ export default class ChatInput {
}
private resetCurrentFormatting() {
return document.execCommand('removeFormat', false, null);
}
private resetCurrentFontFormatting() {
return document.execCommand('fontName', false, 'Roboto');
}
@ -1823,7 +1843,7 @@ export default class ChatInput { @@ -1823,7 +1843,7 @@ export default class ChatInput {
// document.execCommand('styleWithCSS', false, 'true');
setTimeout(() => {
if(document.activeElement === this.messageInput) {
this.resetCurrentFormatting();
this.resetCurrentFontFormatting();
}
}, 0);
// document.execCommand('styleWithCSS', false, 'false');

5
src/components/chat/replyKeyboard.ts

@ -19,6 +19,7 @@ import safeAssign from '../../helpers/object/safeAssign'; @@ -19,6 +19,7 @@ import safeAssign from '../../helpers/object/safeAssign';
import setInnerHTML from '../../helpers/dom/setInnerHTML';
import wrapEmojiText from '../../lib/richTextProcessor/wrapEmojiText';
import {AppManagers} from '../../lib/appManagers/managers';
import {attachClickEvent} from '../../helpers/dom/clickEvent';
export default class ReplyKeyboard extends DropdownHover {
private static BASE_CLASS = 'reply-keyboard';
@ -74,7 +75,7 @@ export default class ReplyKeyboard extends DropdownHover { @@ -74,7 +75,7 @@ export default class ReplyKeyboard extends DropdownHover {
}
});
this.listenerSetter.add(this.element)('click', (e) => {
attachClickEvent(this.element, (e) => {
const target = findUpClassName(e.target, 'btn');
if(!target) {
return;
@ -103,7 +104,7 @@ export default class ReplyKeyboard extends DropdownHover { @@ -103,7 +104,7 @@ export default class ReplyKeyboard extends DropdownHover {
}
this.toggle(false);
});
}, {listenerSetter: this.listenerSetter});
return super.init();
}

6
src/components/chat/selection.ts

@ -533,7 +533,7 @@ export class SearchSelection extends AppSelection { @@ -533,7 +533,7 @@ export class SearchSelection extends AppSelection {
private isPrivate: boolean;
constructor(private searchSuper: AppSearchSuper, managers: AppManagers) {
constructor(private searchSuper: AppSearchSuper, managers: AppManagers, listenerSetter: ListenerSetter) {
super({
managers,
verifyTarget: (e, target) => !!target && this.isSelecting,
@ -544,7 +544,7 @@ export class SearchSelection extends AppSelection { @@ -544,7 +544,7 @@ export class SearchSelection extends AppSelection {
});
this.isPrivate = !searchSuper.showSender;
this.attachListeners(searchSuper.container, new ListenerSetter());
this.attachListeners(searchSuper.container, listenerSetter);
}
/* public appendCheckbox(element: HTMLElement, checkboxField: CheckboxField) {
@ -615,7 +615,7 @@ export class SearchSelection extends AppSelection { @@ -615,7 +615,7 @@ export class SearchSelection extends AppSelection {
this.selectionContainer.classList.add(BASE_CLASS + '-container');
const btnCancel = ButtonIcon(`close ${BASE_CLASS}-cancel`, {noRipple: true});
this.listenerSetter.add(btnCancel)('click', () => this.cancelSelection(), {once: true});
attachClickEvent(btnCancel, () => this.cancelSelection(), {listenerSetter: this.listenerSetter, once: true});
this.selectionCountEl = document.createElement('div');
this.selectionCountEl.classList.add(BASE_CLASS + '-count');

33
src/components/checkboxField.ts

@ -4,6 +4,7 @@ @@ -4,6 +4,7 @@
* https://github.com/morethanwords/tweb/blob/master/LICENSE
*/
import type ListenerSetter from '../helpers/listenerSetter';
import ripple from './ripple';
import {LangPackKey, _i18n} from '../lib/langPack';
import getDeepProperty from '../helpers/object/getDeepProperty';
@ -23,6 +24,7 @@ export type CheckboxFieldOptions = { @@ -23,6 +24,7 @@ export type CheckboxFieldOptions = {
restriction?: boolean,
withRipple?: boolean,
withHover?: boolean,
listenerSetter?: ListenerSetter
};
export default class CheckboxField {
public input: HTMLInputElement;
@ -57,7 +59,24 @@ export default class CheckboxField { @@ -57,7 +59,24 @@ export default class CheckboxField {
}
if(options.stateKey) {
let loaded = false;
const onChange = () => {
if(!loaded) {
return;
}
let value: any;
if(options.stateValues) {
value = options.stateValues[input.checked ? 1 : 0];
} else {
value = input.checked;
}
rootScope.managers.appStateManager.setByKey(options.stateKey, value);
};
apiManagerProxy.getState().then((state) => {
loaded = true;
const stateValue = getDeepProperty(state, options.stateKey);
let checked: boolean;
if(options.stateValues) {
@ -67,18 +86,10 @@ export default class CheckboxField { @@ -67,18 +86,10 @@ export default class CheckboxField {
}
this.setValueSilently(checked);
input.addEventListener('change', () => {
let value: any;
if(options.stateValues) {
value = options.stateValues[input.checked ? 1 : 0];
} else {
value = input.checked;
}
rootScope.managers.appStateManager.setByKey(options.stateKey, value);
});
});
if(options.listenerSetter) options.listenerSetter.add(input)('change', onChange);
else input.addEventListener('change', onChange);
}
let span: HTMLSpanElement;

17
src/components/emoticonsDropdown/index.ts

@ -29,6 +29,7 @@ import pause from '../../helpers/schedulers/pause'; @@ -29,6 +29,7 @@ import pause from '../../helpers/schedulers/pause';
import {IS_APPLE_MOBILE} from '../../environment/userAgent';
import {AppManagers} from '../../lib/appManagers/managers';
import type LazyLoadQueueIntersector from '../lazyLoadQueueIntersector';
import {simulateClickEvent} from '../../helpers/dom/clickEvent';
export const EMOTICONSSTICKERGROUP = 'emoticons-dropdown';
@ -176,7 +177,7 @@ export class EmoticonsDropdown extends DropdownHover { @@ -176,7 +177,7 @@ export class EmoticonsDropdown extends DropdownHover {
(this.tabsEl.children[1] as HTMLElement).classList.add('hide');
}
(this.tabsEl.children[INIT_TAB_ID + 1] as HTMLLIElement).click(); // set emoji tab
simulateClickEvent(this.tabsEl.children[INIT_TAB_ID + 1] as HTMLElement); // set emoji tab
if(this.tabs[INIT_TAB_ID].init) {
this.tabs[INIT_TAB_ID].init(); // onTransitionEnd не вызовется, т.к. это первая открытая вкладка
}
@ -187,6 +188,10 @@ export class EmoticonsDropdown extends DropdownHover { @@ -187,6 +188,10 @@ export class EmoticonsDropdown extends DropdownHover {
return super.init();
}
public getElement() {
return this.element;
}
private onSelectTabClick = (id: number) => {
if(this.tabId === id) {
return;
@ -248,11 +253,11 @@ export class EmoticonsDropdown extends DropdownHover { @@ -248,11 +253,11 @@ export class EmoticonsDropdown extends DropdownHover {
setActive(which);
if(menuScroll) {
if(which < menu.childElementCount - 4) {
menuScroll.container.scrollLeft = (which - 3) * 47;
} else {
menuScroll.container.scrollLeft = which * 47;
}
menuScroll.scrollIntoViewNew({
element: menu.children[which] as HTMLElement,
position: 'center',
axis: 'x'
});
}
});

3
src/components/emoticonsDropdown/tabs/emoji.ts

@ -24,6 +24,7 @@ import {AppManagers} from '../../../lib/appManagers/managers'; @@ -24,6 +24,7 @@ import {AppManagers} from '../../../lib/appManagers/managers';
import fixEmoji from '../../../lib/richTextProcessor/fixEmoji';
import wrapEmojiText from '../../../lib/richTextProcessor/wrapEmojiText';
import wrapSingleEmoji from '../../../lib/richTextProcessor/wrapSingleEmoji';
import {attachClickEvent} from '../../../helpers/dom/clickEvent';
const loadedURLs: Set<string> = new Set();
export function appendEmoji(emoji: string, container: HTMLElement, prepend = false, unify = false) {
@ -258,7 +259,7 @@ export default class EmojiTab implements EmoticonsTab { @@ -258,7 +259,7 @@ export default class EmojiTab implements EmoticonsTab {
});
});
this.content.addEventListener('click', this.onContentClick);
attachClickEvent(this.content, this.onContentClick);
this.init = null;
rootScope.addEventListener('emoji_recent', (emoji) => {

3
src/components/emoticonsDropdown/tabs/gifs.ts

@ -9,6 +9,7 @@ import GifsMasonry from '../../gifsMasonry'; @@ -9,6 +9,7 @@ import GifsMasonry from '../../gifsMasonry';
import Scrollable from '../../scrollable';
import {putPreloader} from '../../putPreloader';
import {AppManagers} from '../../../lib/appManagers/managers';
import {attachClickEvent} from '../../../helpers/dom/clickEvent';
export default class GifsTab implements EmoticonsTab {
private content: HTMLElement;
@ -20,7 +21,7 @@ export default class GifsTab implements EmoticonsTab { @@ -20,7 +21,7 @@ export default class GifsTab implements EmoticonsTab {
init() {
this.content = document.getElementById('content-gifs');
const gifsContainer = this.content.firstElementChild as HTMLDivElement;
gifsContainer.addEventListener('click', EmoticonsDropdown.onMediaClick);
attachClickEvent(gifsContainer, EmoticonsDropdown.onMediaClick);
const scroll = new Scrollable(this.content, 'GIFS');
const masonry = new GifsMasonry(gifsContainer, EMOTICONSSTICKERGROUP, scroll);

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

@ -22,62 +22,75 @@ import PopupStickers from '../../popups/stickers'; @@ -22,62 +22,75 @@ import PopupStickers from '../../popups/stickers';
import Scrollable, {ScrollableX} from '../../scrollable';
import StickyIntersector from '../../stickyIntersector';
import {wrapSticker, wrapStickerSetThumb} from '../../wrappers';
import findAndSplice from '../../../helpers/array/findAndSplice';
import {attachClickEvent} from '../../../helpers/dom/clickEvent';
import positionElementByIndex from '../../../helpers/dom/positionElementByIndex';
import noop from '../../../helpers/noop';
import ButtonIcon from '../../buttonIcon';
import confirmationPopup from '../../confirmationPopup';
import VisibilityIntersector, {OnVisibilityChange} from '../../visibilityIntersector';
export class SuperStickerRenderer {
public lazyLoadQueue: LazyLoadQueueRepeat;
private animatedDivs: Set<HTMLDivElement> = new Set();
private animated: Set<HTMLElement> = new Set();
constructor(
private regularLazyLoadQueue: LazyLoadQueue,
private group: string,
private managers: AppManagers
private managers: AppManagers,
private options?: IntersectionObserverInit
) {
this.lazyLoadQueue = new LazyLoadQueueRepeat(undefined, (target, visible) => {
this.lazyLoadQueue = new LazyLoadQueueRepeat(undefined, ({target, visible}) => {
if(!visible) {
this.processInvisibleDiv(target as HTMLDivElement);
this.processInvisible(target);
}
});
}, options);
}
public clear() {
this.lazyLoadQueue.clear();
}
public renderSticker(doc: MyDocument, div?: HTMLDivElement, loadPromises?: Promise<any>[]) {
if(!div) {
div = document.createElement('div');
div.classList.add('grid-item', 'super-sticker');
public renderSticker(doc: MyDocument, element?: HTMLElement, loadPromises?: Promise<any>[]) {
if(!element) {
element = document.createElement('div');
element.classList.add('grid-item', 'super-sticker');
element.dataset.docId = '' + doc.id;
if(doc.animated) {
this.observeAnimatedDiv(div);
this.observeAnimated(element);
}
}
// * This will wrap only a thumb
wrapSticker({
/* !doc.animated && */wrapSticker({
doc,
div,
div: element,
lazyLoadQueue: this.regularLazyLoadQueue,
group: this.group,
onlyThumb: doc.animated,
loadPromises
});
return div;
return element;
}
public observeAnimatedDiv(div: HTMLDivElement) {
this.animatedDivs.add(div);
public observeAnimated(element: HTMLElement) {
this.animated.add(element);
this.lazyLoadQueue.observe({
div,
load: this.processVisibleDiv
div: element,
load: this.processVisible
});
}
private checkAnimationContainer = (div: HTMLElement, visible: boolean) => {
public unobserveAnimated(element: HTMLElement) {
this.animated.delete(element);
this.lazyLoadQueue.unobserve(element);
}
private checkAnimationContainer = (element: HTMLElement, visible: boolean) => {
// console.error('checkAnimationContainer', div, visible);
const players = animationIntersector.getAnimations(div);
const players = animationIntersector.getAnimations(element);
players.forEach((player) => {
if(!visible) {
animationIntersector.checkAnimation(player, true, true);
@ -87,8 +100,8 @@ export class SuperStickerRenderer { @@ -87,8 +100,8 @@ export class SuperStickerRenderer {
});
};
private processVisibleDiv = async(div: HTMLElement) => {
const docId = div.dataset.docId;
private processVisible = async(element: HTMLElement) => {
const docId = element.dataset.docId;
const doc = await this.managers.appDocsManager.getDoc(docId);
const size = mediaSizes.active.esgSticker.width;
@ -97,7 +110,7 @@ export class SuperStickerRenderer { @@ -97,7 +110,7 @@ export class SuperStickerRenderer {
const promise = wrapSticker({
doc,
div: div as HTMLDivElement,
div: element,
width: size,
height: size,
lazyLoadQueue: null,
@ -109,7 +122,7 @@ export class SuperStickerRenderer { @@ -109,7 +122,7 @@ export class SuperStickerRenderer {
promise.then(() => {
// clearTimeout(timeout);
this.checkAnimationContainer(div, this.lazyLoadQueue.intersector.isVisible(div));
this.checkAnimationContainer(element, this.lazyLoadQueue.intersector.isVisible(element));
});
/* let timeout = window.setTimeout(() => {
@ -119,123 +132,152 @@ export class SuperStickerRenderer { @@ -119,123 +132,152 @@ export class SuperStickerRenderer {
return promise;
};
public processInvisibleDiv = async(div: HTMLElement) => {
const docId = div.dataset.docId;
public processInvisible = async(element: HTMLElement) => {
const docId = element.dataset.docId;
const doc = await this.managers.appDocsManager.getDoc(docId);
// console.log('STICKER INvisible:', /* div, */docId);
this.checkAnimationContainer(div, false);
this.checkAnimationContainer(element, false);
div.innerHTML = '';
this.renderSticker(doc, div as HTMLDivElement);
element.textContent = '';
this.renderSticker(doc, element as HTMLDivElement);
};
}
type StickersTabCategory = {
elements: {
container: HTMLElement,
title: HTMLElement,
items: HTMLElement,
menuTab: HTMLElement,
menuTabPadding: HTMLElement
},
set: StickerSet.stickerSet,
items: {
document: MyDocument,
element: HTMLElement
}[]
};
const RECENT_STICKERS_COUNT = 20;
export default class StickersTab implements EmoticonsTab {
private content: HTMLElement;
private stickersDiv: HTMLElement;
private stickerSets: {[id: string]: {
stickers: HTMLElement,
tab: HTMLElement
}} = {};
private recentDiv: HTMLElement;
private recentStickers: MyDocument[] = [];
private categories: {[id: string]: StickersTabCategory};
private categoriesMap: Map<HTMLElement, StickersTabCategory>;
private categoriesIntersector: VisibilityIntersector;
private scroll: Scrollable;
private menu: HTMLElement;
private mounted = false;
private queueCategoryPush: {element: HTMLElement, prepend: boolean}[] = [];
private stickyIntersector: StickyIntersector;
private superStickerRenderer: SuperStickerRenderer;
constructor(private managers: AppManagers) {
this.categories = {};
this.categoriesMap = new Map();
}
categoryPush(categoryDiv: HTMLElement, categoryTitle: DocumentFragment | string = '', promise: Promise<MyDocument[]>, prepend?: boolean) {
// if((docs.length % 5) !== 0) categoryDiv.classList.add('not-full');
private createCategory(stickerSet: StickerSet.stickerSet, _title: HTMLElement | DocumentFragment) {
const container = document.createElement('div');
container.classList.add('emoji-category', 'hide');
const itemsDiv = document.createElement('div');
itemsDiv.classList.add('category-items', 'super-stickers');
const items = document.createElement('div');
items.classList.add('category-items', 'super-stickers');
const titleDiv = document.createElement('div');
titleDiv.classList.add('category-title');
const title = document.createElement('div');
title.classList.add('category-title');
title.append(_title);
if(categoryTitle) {
if(typeof(categoryTitle) === 'string') titleDiv.innerHTML = categoryTitle;
else titleDiv.append(categoryTitle);
}
const menuTab = ButtonIcon(undefined, {noRipple: true});
menuTab.classList.add('menu-horizontal-div-item');
const menuTabPadding = document.createElement('div');
menuTabPadding.classList.add('menu-horizontal-div-item-padding');
menuTab.append(menuTabPadding);
const category: StickersTabCategory = {
elements: {
container,
title,
items,
menuTab,
menuTabPadding
},
set: stickerSet,
items: []
};
container.append(title, items);
categoryDiv.append(titleDiv, itemsDiv);
this.categories[stickerSet.id] = category;
this.categoriesMap.set(container, category);
this.stickyIntersector.observeStickyHeaderChanges(categoryDiv);
this.categoriesIntersector.observe(container);
this.stickyIntersector.observeStickyHeaderChanges(container);
this.queueCategoryPush.push({element: categoryDiv, prepend});
return category;
}
private categoryAppendStickers(
category: StickersTabCategory,
promise: Promise<MyDocument[]>
) {
const {container} = category.elements;
promise.then((documents) => {
documents.forEach((doc) => {
// if(doc._ === 'documentEmpty') return;
itemsDiv.append(this.superStickerRenderer.renderSticker(doc));
const isVisible = this.isCategoryVisible(category);
documents.forEach((document) => {
const element = this.superStickerRenderer.renderSticker(document);
category.items.push({document, element});
if(isVisible) {
category.elements.items.append(element);
}
});
if(this.queueCategoryPush.length) {
this.queueCategoryPush.forEach(({element, prepend}) => {
if(prepend) {
if(this.recentDiv.parentElement) {
this.stickersDiv.prepend(element);
this.stickersDiv.prepend(this.recentDiv);
} else {
this.stickersDiv.prepend(element);
}
} else this.stickersDiv.append(element);
});
this.queueCategoryPush.length = 0;
}
this.setCategoryItemsHeight(category);
container.classList.remove('hide');
});
}
return {titleDiv};
private isCategoryVisible(category: StickersTabCategory) {
return this.categoriesIntersector.getVisible().includes(category.elements.container);
}
async renderStickerSet(set: StickerSet.stickerSet, prepend = false) {
const categoryDiv = document.createElement('div');
categoryDiv.classList.add('sticker-category');
categoryDiv.dataset.id = '' + set.id;
categoryDiv.dataset.access_hash = '' + set.access_hash;
private setCategoryItemsHeight(category: StickersTabCategory) {
const containerWidth = this.content.getBoundingClientRect().width - 10;
const stickerSize = mediaSizes.active.esgSticker.width;
const button = document.createElement('button');
button.classList.add('btn-icon', 'menu-horizontal-div-item');
const itemsPerRow = Math.floor(containerWidth / stickerSize);
const rows = Math.ceil(category.items.length / itemsPerRow);
const height = rows * stickerSize;
this.stickerSets[set.id] = {
stickers: categoryDiv,
tab: button
};
category.elements.items.style.minHeight = height + 'px';
}
if(prepend) {
this.menu.insertBefore(button, this.menu.firstElementChild.nextSibling);
} else {
this.menu.append(button);
}
private async renderStickerSet(set: StickerSet.stickerSet, prepend = false) {
const category = this.createCategory(set, wrapEmojiText(set.title));
const {menuTab, menuTabPadding, container} = category.elements;
// stickersScroll.append(categoryDiv);
positionElementByIndex(menuTab, this.menu, prepend ? 1 : 0xFFFF);
const promise = this.managers.appStickersManager.getStickerSet(set);
this.categoryPush(categoryDiv, wrapEmojiText(set.title), promise.then((stickerSet) => stickerSet.documents as MyDocument[]), prepend);
const stickerSet = await promise;
this.categoryAppendStickers(
category,
promise.then((stickerSet) => stickerSet.documents as MyDocument[])
);
// const stickerSet = await promise;
// console.log('got stickerSet', stickerSet, li);
positionElementByIndex(container, this.scroll.container, prepend ? 1 : 0xFFFF, -1);
wrapStickerSetThumb({
set,
container: button,
container: menuTabPadding,
group: EMOTICONSSTICKERGROUP,
lazyLoadQueue: EmoticonsDropdown.lazyLoadQueue,
width: 32,
@ -244,21 +286,17 @@ export default class StickersTab implements EmoticonsTab { @@ -244,21 +286,17 @@ export default class StickersTab implements EmoticonsTab {
});
}
init() {
public init() {
this.content = document.getElementById('content-stickers');
// let stickersDiv = contentStickersDiv.querySelector('.os-content') as HTMLDivElement;
this.recentDiv = document.createElement('div');
this.recentDiv.classList.add('sticker-category', 'stickers-recent');
const menuWrapper = this.content.previousElementSibling as HTMLDivElement;
this.menu = menuWrapper.firstElementChild as HTMLUListElement;
const menuScroll = new ScrollableX(menuWrapper);
this.stickersDiv = document.createElement('div');
this.stickersDiv.classList.add('stickers-categories');
this.content.append(this.stickersDiv);
this.scroll = new Scrollable(this.content, 'STICKERS');
this.scroll.onAdditionalScroll = () => {
setTyping();
};
/* stickersDiv.addEventListener('mouseover', (e) => {
let target = e.target as HTMLElement;
@ -277,30 +315,35 @@ export default class StickersTab implements EmoticonsTab { @@ -277,30 +315,35 @@ export default class StickersTab implements EmoticonsTab {
}
}); */
rootScope.addEventListener('stickers_installed', (e) => {
const set: StickerSet.stickerSet = e;
if(!this.stickerSets[set.id] && this.mounted) {
this.renderStickerSet(set, true);
const onCategoryVisibility: OnVisibilityChange = ({target, visible, entry}) => {
const category = this.categoriesMap.get(target);
// console.log('roll the windows up', category, target, visible, entry);
if(!visible) {
category.elements.items.textContent = '';
} else {
category.elements.items.append(...category.items.map(({element}) => element));
}
});
};
rootScope.addEventListener('stickers_deleted', (e) => {
const set: StickerSet.stickerSet = e;
const intersectionOptions: IntersectionObserverInit = {root: emoticonsDropdown.getElement()};
this.categoriesIntersector = new VisibilityIntersector(onCategoryVisibility, intersectionOptions);
if(this.stickerSets[set.id] && this.mounted) {
const elements = this.stickerSets[set.id];
elements.stickers.remove();
elements.tab.remove();
delete this.stickerSets[set.id];
}
});
const clearCategoryItems = (category: StickersTabCategory) => {
category.elements.items.textContent = '';
category.items.forEach(({element}) => this.superStickerRenderer.unobserveAnimated(element));
category.items.length = 0;
};
this.stickersDiv.addEventListener('click', (e) => {
this.scroll.container.addEventListener('click', (e) => {
const target = e.target as HTMLElement;
if(findUpClassName(target, 'category-title')) {
const el = findUpAttribute(target, 'data-id');
new PopupStickers({id: el.dataset.id, access_hash: el.dataset.access_hash}).show();
const container = findUpClassName(target, 'emoji-category');
const category = this.categoriesMap.get(container);
if(category.set.id === 'recent') {
return;
}
new PopupStickers({id: category.set.id, access_hash: category.set.access_hash}).show();
return;
}
@ -311,12 +354,6 @@ export default class StickersTab implements EmoticonsTab { @@ -311,12 +354,6 @@ export default class StickersTab implements EmoticonsTab {
rootScope.dispatchEvent('choosing_sticker', !cancel);
};
this.scroll = new Scrollable(this.content, 'STICKERS');
this.scroll.setVirtualContainer(this.stickersDiv);
this.scroll.onAdditionalScroll = () => {
setTyping();
};
emoticonsDropdown.addEventListener('closed', () => {
setTyping(true);
});
@ -325,24 +362,43 @@ export default class StickersTab implements EmoticonsTab { @@ -325,24 +362,43 @@ export default class StickersTab implements EmoticonsTab {
setTyping();
});
this.stickyIntersector = EmoticonsDropdown.menuOnClick(this.menu, this.scroll, menuScroll).stickyIntersector;
const {stickyIntersector, setActive} = EmoticonsDropdown.menuOnClick(this.menu, this.scroll, menuScroll);
this.stickyIntersector = stickyIntersector;
const preloader = putPreloader(this.content, true);
Promise.all([
this.managers.appStickersManager.getRecentStickers().then((stickers) => {
this.recentStickers = stickers.stickers.slice(0, 20) as MyDocument[];
const recentCategory = this.createCategory({id: 'recent'} as any, i18n('Stickers.Recent'));
recentCategory.elements.title.classList.add('disable-hover');
recentCategory.elements.menuTab.classList.add('tgico-recent', 'active');
recentCategory.elements.menuTabPadding.remove();
this.toggleRecentCategory(recentCategory, false);
const clearButton = ButtonIcon('close', {noRipple: true});
recentCategory.elements.title.append(clearButton);
attachClickEvent(clearButton, () => {
confirmationPopup({
titleLangKey: 'ClearRecentStickersAlertTitle',
descriptionLangKey: 'ClearRecentStickersAlertMessage',
button: {
langKey: 'Clear'
}
}).then(() => {
this.managers.appStickersManager.clearRecentStickers();
}, noop);
});
// stickersScroll.prepend(categoryDiv);
const onRecentStickers = (stickers: MyDocument[]) => {
const sliced = stickers.slice(0, RECENT_STICKERS_COUNT) as MyDocument[];
this.stickerSets['recent'] = {
stickers: this.recentDiv,
tab: this.menu.firstElementChild as HTMLElement
};
clearCategoryItems(recentCategory);
this.toggleRecentCategory(recentCategory, !!sliced.length);
this.categoryAppendStickers(recentCategory, Promise.resolve(sliced));
};
Promise.all([
this.managers.appStickersManager.getRecentStickers().then((stickers) => {
preloader.remove();
const {titleDiv} = this.categoryPush(this.recentDiv, '', Promise.resolve(this.recentStickers), true);
titleDiv.append(i18n('Stickers.Recent'));
onRecentStickers(stickers.stickers as MyDocument[]);
}),
this.managers.appStickersManager.getAllStickers().then((res) => {
@ -355,41 +411,114 @@ export default class StickersTab implements EmoticonsTab { @@ -355,41 +411,114 @@ export default class StickersTab implements EmoticonsTab {
]).finally(() => {
this.mounted = true;
setTyping();
setActive(0);
});
this.superStickerRenderer = new SuperStickerRenderer(EmoticonsDropdown.lazyLoadQueue, EMOTICONSSTICKERGROUP, this.managers);
this.superStickerRenderer = new SuperStickerRenderer(EmoticonsDropdown.lazyLoadQueue, EMOTICONSSTICKERGROUP, this.managers, intersectionOptions);
const rendererLazyLoadQueue = this.superStickerRenderer.lazyLoadQueue;
emoticonsDropdown.addLazyLoadQueueRepeat(rendererLazyLoadQueue, this.superStickerRenderer.processInvisible);
// emoticonsDropdown.addEventListener('close', () => {
// this.categoriesIntersector.lock();
// });
emoticonsDropdown.addLazyLoadQueueRepeat(this.superStickerRenderer.lazyLoadQueue, this.superStickerRenderer.processInvisibleDiv);
// emoticonsDropdown.addEventListener('closed', () => {
// for(const [container] of this.categoriesMap) {
// onCategoryVisibility(container, false);
// }
// });
/* setInterval(() => {
// @ts-ignore
const players = Object.values(lottieLoader.players).filter((p) => p.width === 80);
// emoticonsDropdown.addEventListener('opened', () => {
// this.categoriesIntersector.unlockAndRefresh();
// });
console.log('STICKERS RENDERED IN PANEL:', players.length, players.filter((p) => !p.paused).length, this.superStickerRenderer.lazyLoadQueue.intersector.getVisible().length);
}, .25e3); */
// setInterval(() => {
// // @ts-ignore
// const players = Object.values(lottieLoader.players).filter((p) => p.width >= 80);
// console.log(
// 'STICKERS RENDERED IN PANEL:',
// players.length,
// players.filter((p) => !p.paused).length,
// rendererLazyLoadQueue.intersector.getVisible().length
// );
// }, .25e3);
rootScope.addEventListener('stickers_installed', (set) => {
if(!this.categories[set.id] && this.mounted) {
this.renderStickerSet(set, true);
}
});
rootScope.addEventListener('stickers_deleted', ({id}) => {
const category = this.categories[id];
if(category && this.mounted) {
category.elements.container.remove();
category.elements.menuTab.remove();
this.categoriesIntersector.unobserve(category.elements.container);
clearCategoryItems(category);
delete this.categories[id];
this.categoriesMap.delete(category.elements.container);
}
});
rootScope.addEventListener('stickers_recent', (stickers) => {
if(this.mounted) {
onRecentStickers(stickers);
}
});
const resizeCategories = () => {
for(const [container, category] of this.categoriesMap) {
this.setCategoryItemsHeight(category);
}
};
mediaSizes.addEventListener('resize', resizeCategories);
emoticonsDropdown.addEventListener('opened', resizeCategories);
this.init = null;
}
pushRecentSticker(doc: MyDocument) {
this.managers.appStickersManager.pushRecentSticker(doc);
private toggleRecentCategory(category: StickersTabCategory, visible: boolean) {
if(!visible) {
category.elements.menuTab.remove();
category.elements.container.remove();
} else {
positionElementByIndex(category.elements.menuTab, this.menu, 0);
positionElementByIndex(category.elements.container, this.scroll.container, 0);
}
// category.elements.container.classList.toggle('hide', !visible);
}
public pushRecentSticker(doc: MyDocument) {
this.managers.appStickersManager.pushRecentSticker(doc.id);
if(!this.recentDiv?.parentElement) {
const category = this.categories['recent'];
if(!category) {
return;
}
let div = this.recentDiv.querySelector(`[data-doc-id="${doc.id}"]`);
if(!div) {
div = this.superStickerRenderer.renderSticker(doc);
const items = category.elements.items;
let item = findAndSplice(category.items, (item) => item.document.id === doc.id);
if(!item) {
item = {
element: this.superStickerRenderer.renderSticker(doc),
document: doc
};
}
const items = this.recentDiv.querySelector('.category-items');
items.prepend(div);
if(items.childElementCount > 20) {
(Array.from(items.children) as HTMLElement[]).slice(20).forEach((el) => el.remove());
category.items.unshift(item);
if(items.childElementCount) items.prepend(item.element);
if(items.childElementCount > RECENT_STICKERS_COUNT) {
(Array.from(items.children) as HTMLElement[]).slice(RECENT_STICKERS_COUNT).forEach((el) => el.remove());
}
this.setCategoryItemsHeight(category);
this.toggleRecentCategory(category, true);
}
onClose() {

2
src/components/gifsMasonry.ts

@ -34,7 +34,7 @@ export default class GifsMasonry { @@ -34,7 +34,7 @@ export default class GifsMasonry {
) {
this.managers = rootScope.managers;
this.lazyLoadQueue = new LazyLoadQueueRepeat2(undefined, (target, visible) => {
this.lazyLoadQueue = new LazyLoadQueueRepeat2(undefined, ({target, visible}) => {
if(visible) {
this.processVisibleDiv(target);
} else {

11
src/components/horizontalMenu.ts

@ -11,6 +11,8 @@ import {fastRaf} from '../helpers/schedulers'; @@ -11,6 +11,8 @@ import {fastRaf} from '../helpers/schedulers';
import {FocusDirection} from '../helpers/fastSmoothScroll';
import findUpAsChild from '../helpers/dom/findUpAsChild';
import whichChild from '../helpers/dom/whichChild';
import ListenerSetter from '../helpers/listenerSetter';
import {attachClickEvent} from '../helpers/dom/clickEvent';
export function horizontalMenu(
tabs: HTMLElement,
@ -18,9 +20,10 @@ export function horizontalMenu( @@ -18,9 +20,10 @@ export function horizontalMenu(
onClick?: (id: number, tabContent: HTMLDivElement, animate: boolean) => void | boolean | Promise<void | boolean>,
onTransitionEnd?: () => void,
transitionTime = 250,
scrollableX?: ScrollableX
scrollableX?: ScrollableX,
listenerSetter?: ListenerSetter
) {
const selectTab = TransitionSlider(content, tabs || content.dataset.animation === 'tabs' ? 'tabs' : 'navigation', transitionTime, onTransitionEnd);
const selectTab = TransitionSlider(content, tabs || content.dataset.animation === 'tabs' ? 'tabs' : 'navigation', transitionTime, onTransitionEnd, undefined, listenerSetter);
if(!tabs) {
return selectTab;
@ -110,7 +113,7 @@ export function horizontalMenu( @@ -110,7 +113,7 @@ export function horizontalMenu(
// const tagName = tabs.classList.contains('menu-horizontal-div') ? 'BUTTON' : 'LI';
const tagName = tabs.firstElementChild.tagName;
tabs.addEventListener('click', function(e) {
attachClickEvent(tabs, (e) => {
let target = e.target as HTMLElement;
target = findUpAsChild(target, tabs);
@ -130,7 +133,7 @@ export function horizontalMenu( @@ -130,7 +133,7 @@ export function horizontalMenu(
}
selectTarget(target, id);
});
}, {listenerSetter});
return proxy;
}

4
src/components/lazyLoadQueue.ts

@ -4,7 +4,7 @@ @@ -4,7 +4,7 @@
* https://github.com/morethanwords/tweb/blob/master/LICENSE
*/
import VisibilityIntersector from './visibilityIntersector';
import VisibilityIntersector, {OnVisibilityChangeItem} from './visibilityIntersector';
import findAndSpliceAll from '../helpers/array/findAndSpliceAll';
import findAndSplice from '../helpers/array/findAndSplice';
import LazyLoadQueueIntersector, {LazyLoadElement} from './lazyLoadQueueIntersector';
@ -16,7 +16,7 @@ export default class LazyLoadQueue extends LazyLoadQueueIntersector { @@ -16,7 +16,7 @@ export default class LazyLoadQueue extends LazyLoadQueueIntersector {
this.intersector = new VisibilityIntersector(this.onVisibilityChange);
}
private onVisibilityChange = (target: HTMLElement, visible: boolean) => {
private onVisibilityChange = ({target, visible}: OnVisibilityChangeItem) => {
if(visible) {
/* if(DEBUG) {
this.log('isIntersecting', target);

9
src/components/lazyLoadQueueRepeat.ts

@ -11,10 +11,11 @@ import VisibilityIntersector, {OnVisibilityChange} from './visibilityIntersector @@ -11,10 +11,11 @@ import VisibilityIntersector, {OnVisibilityChange} from './visibilityIntersector
export default class LazyLoadQueueRepeat extends LazyLoadQueueIntersector {
private _queue: Map<HTMLElement, LazyLoadElement> = new Map();
constructor(parallelLimit?: number, protected onVisibilityChange?: OnVisibilityChange) {
constructor(parallelLimit?: number, protected onVisibilityChange?: OnVisibilityChange, options?: IntersectionObserverInit) {
super(parallelLimit);
this.intersector = new VisibilityIntersector((target, visible) => {
this.intersector = new VisibilityIntersector((item) => {
const {target, visible} = item;
const spliced = findAndSpliceAll(this.queue, (i) => i.div === target);
if(visible) {
const items = spliced.length ? spliced : [this._queue.get(target)];
@ -23,9 +24,9 @@ export default class LazyLoadQueueRepeat extends LazyLoadQueueIntersector { @@ -23,9 +24,9 @@ export default class LazyLoadQueueRepeat extends LazyLoadQueueIntersector {
});
}
this.onVisibilityChange && this.onVisibilityChange(target, visible);
this.onVisibilityChange && this.onVisibilityChange(item);
this.setProcessQueueTimeout();
});
}, options);
}
public clear() {

5
src/components/lazyLoadQueueRepeat2.ts

@ -12,7 +12,8 @@ export default class LazyLoadQueueRepeat2 extends LazyLoadQueueIntersector { @@ -12,7 +12,8 @@ export default class LazyLoadQueueRepeat2 extends LazyLoadQueueIntersector {
constructor(parallelLimit?: number, protected onVisibilityChange?: OnVisibilityChange) {
super(parallelLimit);
this.intersector = new VisibilityIntersector((target, visible) => {
this.intersector = new VisibilityIntersector((item) => {
const {target, visible} = item;
const spliced = findAndSpliceAll(this.queue, (i) => i.div === target);
if(visible && spliced.length) {
spliced.forEach((item) => {
@ -20,7 +21,7 @@ export default class LazyLoadQueueRepeat2 extends LazyLoadQueueIntersector { @@ -20,7 +21,7 @@ export default class LazyLoadQueueRepeat2 extends LazyLoadQueueIntersector {
});
}
this.onVisibilityChange && this.onVisibilityChange(target, visible);
this.onVisibilityChange && this.onVisibilityChange(item);
this.setProcessQueueTimeout();
});
}

16
src/components/peerProfile.ts

@ -104,7 +104,8 @@ export default class PeerProfile { @@ -104,7 +104,8 @@ export default class PeerProfile {
const full = await this.managers.appProfileManager.getProfileByPeerId(this.peerId);
copyTextToClipboard(full.about);
toast(I18n.format('BioCopied', true));
}
},
listenerSetter: this.listenerSetter
});
this.bio.title.classList.add('pre-wrap');
@ -117,7 +118,8 @@ export default class PeerProfile { @@ -117,7 +118,8 @@ export default class PeerProfile {
const peer: Channel | User.user = await this.managers.appPeersManager.getPeer(this.peerId);
copyTextToClipboard('@' + peer.username);
toast(I18n.format('UsernameCopied', true));
}
},
listenerSetter: this.listenerSetter
});
this.phone = new Row({
@ -128,7 +130,8 @@ export default class PeerProfile { @@ -128,7 +130,8 @@ export default class PeerProfile {
const peer: User = await this.managers.appUsersManager.getUser(this.peerId);
copyTextToClipboard('+' + peer.phone);
toast(I18n.format('PhoneCopied', true));
}
},
listenerSetter: this.listenerSetter
});
this.link = new Row({
@ -141,7 +144,8 @@ export default class PeerProfile { @@ -141,7 +144,8 @@ export default class PeerProfile {
// copyTextToClipboard(chatFull.exported_invite.link);
toast(I18n.format('LinkCopied', true));
// });
}
},
listenerSetter: this.listenerSetter
});
this.location = new Row({
@ -163,7 +167,8 @@ export default class PeerProfile { @@ -163,7 +167,8 @@ export default class PeerProfile {
this.notifications = new Row({
checkboxField: new CheckboxField({toggle: true}),
titleLangKey: 'Notifications',
icon: 'unmute'
icon: 'unmute',
listenerSetter: this.listenerSetter
});
listenerSetter.add(this.notifications.checkboxField.input)('change', (e) => {
@ -457,5 +462,6 @@ export default class PeerProfile { @@ -457,5 +462,6 @@ export default class PeerProfile {
public destroy() {
this.clearSetMoreDetailsTimeout();
clearInterval(this.setPeerStatusInterval);
this.avatars?.cleanup();
}
}

1
src/components/peerProfileAvatars.ts

@ -409,5 +409,6 @@ export default class PeerProfileAvatars { @@ -409,5 +409,6 @@ export default class PeerProfileAvatars {
public cleanup() {
this.listenerSetter.removeAll();
this.swipeHandler.removeListeners();
this.intersectionObserver?.disconnect();
}
}

4
src/components/poll.ts

@ -16,7 +16,7 @@ import {fastRaf} from '../helpers/schedulers'; @@ -16,7 +16,7 @@ import {fastRaf} from '../helpers/schedulers';
import SetTransition from './singleTransition';
import findUpClassName from '../helpers/dom/findUpClassName';
import cancelEvent from '../helpers/dom/cancelEvent';
import {attachClickEvent, detachClickEvent} from '../helpers/dom/clickEvent';
import {attachClickEvent, detachClickEvent, simulateClickEvent} from '../helpers/dom/clickEvent';
import replaceContent from '../helpers/dom/replaceContent';
import windowSize from '../helpers/windowSize';
import {Message, MessageMedia, Poll, PollResults} from '../layer';
@ -470,7 +470,7 @@ export default class PollElement extends HTMLElement { @@ -470,7 +470,7 @@ export default class PollElement extends HTMLElement {
if(this.sentVote) {
const correctResult = results.results.find((r) => r.pFlags.correct);
if(correctResult && !correctResult.pFlags.chosen) {
toggleHint.click();
simulateClickEvent(toggleHint);
}
}
}

142
src/components/popups/payment.ts

@ -52,6 +52,7 @@ const icons = [ @@ -52,6 +52,7 @@ const icons = [
'mastercard',
'visa',
'unionpay',
'mir',
'logo'
];
@ -134,17 +135,6 @@ export default class PopupPayment extends PopupElement { @@ -134,17 +135,6 @@ export default class PopupPayment extends PopupElement {
}
this.hide();
showSuccessToast();
};
const showSuccessToast = () => {
toastNew({
langPackKey: 'PaymentInfoHint',
langPackArguments: [
paymentsWrapCurrencyAmount(getTotalTotal(), currency),
wrapEmojiText(title)
]
});
};
let {paymentForm, message} = this;
@ -182,7 +172,7 @@ export default class PopupPayment extends PopupElement { @@ -182,7 +172,7 @@ export default class PopupPayment extends PopupElement {
let photoEl: HTMLElement;
if(photo) {
photoEl = document.createElement('div');
photoEl.classList.add(detailsClassName + '-photo', 'media-container-cover');
photoEl.classList.add(detailsClassName + '-photo', 'media-container-contain');
wrapPhoto({
photo: photo,
container: photoEl,
@ -238,7 +228,11 @@ export default class PopupPayment extends PopupElement { @@ -238,7 +228,11 @@ export default class PopupPayment extends PopupElement {
wrapPeerTitle({peerId: paymentForm.provider_id.toPeerId()})
]);
// console.log(paymentForm, lastRequestedInfo);
console.log(paymentForm, lastRequestedInfo);
await peerTitle.update({peerId: paymentForm.bot_id.toPeerId()});
preloaderContainer.remove();
this.element.classList.remove('is-loading');
const wrapAmount = (amount: string | number, skipSymbol?: boolean) => {
return paymentsWrapCurrencyAmount(amount, currency, skipSymbol);
@ -274,8 +268,8 @@ export default class PopupPayment extends PopupElement { @@ -274,8 +268,8 @@ export default class PopupPayment extends PopupElement {
const _label = makeLabel();
_label.left.textContent = label;
const wrappedAmount = wrapAmount(Math.abs(+amount));
_label.right.textContent = (amount < 0 ? '-' : '') + wrappedAmount;
const wrappedAmount = wrapAmount(amount);
_label.right.textContent = wrappedAmount;
return _label.label;
});
@ -303,7 +297,7 @@ export default class PopupPayment extends PopupElement { @@ -303,7 +297,7 @@ export default class PopupPayment extends PopupElement {
_i18n(totalLabel.left, 'PaymentTransactionTotal');
const totalAmount = accumulate(invoice.prices.map(({amount}) => +amount), 0);
const canTip = invoice.max_tip_amount !== undefined;
const canTip = (invoice.max_tip_amount !== undefined && !isReceipt) || !!(paymentForm as PaymentsPaymentReceipt).tip_amount;
if(canTip) {
const tipsClassName = className + '-tips';
@ -331,7 +325,7 @@ export default class PopupPayment extends PopupElement { @@ -331,7 +325,7 @@ export default class PopupPayment extends PopupElement {
placeCaretAtEnd(input);
}
unsetActiveTip();
unsetActiveTip && unsetActiveTip();
const tipEl = this.tipButtonsMap.get(amount);
if(tipEl) {
tipEl.classList.add('active');
@ -350,7 +344,12 @@ export default class PopupPayment extends PopupElement { @@ -350,7 +344,12 @@ export default class PopupPayment extends PopupElement {
input.classList.add('input-clear', tipsClassName + '-input');
tipsLabel.right.append(input);
tipsLabel.label.style.cursor = 'text';
if(!isReceipt) {
tipsLabel.label.style.cursor = 'text';
} else {
tipsLabel.label.classList.add('disable-hover');
}
tipsLabel.label.addEventListener('mousedown', (e) => {
if(!findUpAsChild(e.target, input)) {
placeCaretAtEnd(input);
@ -399,53 +398,58 @@ export default class PopupPayment extends PopupElement { @@ -399,53 +398,58 @@ export default class PopupPayment extends PopupElement {
pricesElements.push(tipsLabel.label);
//
const tipsEl = document.createElement('div');
tipsEl.classList.add(tipsClassName);
const tipClassName = tipsClassName + '-tip';
const tipButtons = invoice.suggested_tip_amounts.map((tipAmount) => {
const button = Button(tipClassName, {noRipple: true});
button.textContent = wrapAmount(tipAmount);
this.tipButtonsMap.set(+tipAmount, button);
return button;
});
let unsetActiveTip: () => void;
if(!isReceipt) {
const tipsEl = document.createElement('div');
tipsEl.classList.add(tipsClassName);
const tipClassName = tipsClassName + '-tip';
const tipButtons = invoice.suggested_tip_amounts.map((tipAmount) => {
const button = Button(tipClassName, {noRipple: true});
button.textContent = wrapAmount(tipAmount);
this.tipButtonsMap.set(+tipAmount, button);
return button;
});
const unsetActiveTip = () => {
const prevTipEl = tipsEl.querySelector('.active');
if(prevTipEl) {
prevTipEl.classList.remove('active');
}
};
unsetActiveTip = () => {
const prevTipEl = tipsEl.querySelector('.active');
if(prevTipEl) {
prevTipEl.classList.remove('active');
}
};
attachClickEvent(tipsEl, (e) => {
const tipEl = findUpClassName(e.target, tipClassName);
if(!tipEl) {
return;
}
attachClickEvent(tipsEl, (e) => {
const tipEl = findUpClassName(e.target, tipClassName);
if(!tipEl) {
return;
}
let tipAmount = 0;
if(tipEl.classList.contains('active')) {
tipEl.classList.remove('active');
} else {
unsetActiveTip();
tipEl.classList.add('active');
let tipAmount = 0;
if(tipEl.classList.contains('active')) {
tipEl.classList.remove('active');
} else {
unsetActiveTip();
tipEl.classList.add('active');
for(const [amount, el] of this.tipButtonsMap) {
if(el === tipEl) {
tipAmount = amount;
break;
for(const [amount, el] of this.tipButtonsMap) {
if(el === tipEl) {
tipAmount = amount;
break;
}
}
}
}
setInputValue(tipAmount);
});
setInputValue(tipAmount);
});
setInputValue(0);
setInputValue(0);
tipsEl.append(...tipButtons);
pricesElements.push(tipsEl);
tipsEl.append(...tipButtons);
pricesElements.push(tipsEl);
} else {
setInputValue((paymentForm as PaymentsPaymentReceipt).tip_amount);
}
} else {
setTotal();
}
@ -476,6 +480,7 @@ export default class PopupPayment extends PopupElement { @@ -476,6 +480,7 @@ export default class PopupPayment extends PopupElement {
options.subtitleLangKey = options.titleLangKey;
}
options.noWrap = true;
const row = new Row(options);
row.container.classList.add(className + '-row');
@ -489,7 +494,7 @@ export default class PopupPayment extends PopupElement { @@ -489,7 +494,7 @@ export default class PopupPayment extends PopupElement {
const setRowTitle = (row: Row, textContent: string) => {
row.title.textContent = textContent;
if(!textContent) {
const e = I18n.weakMap.get(row.subtitle) as I18n.IntlElement;
const e = I18n.weakMap.get(row.subtitle.firstElementChild as HTMLElement) as I18n.IntlElement;
row.title.append(i18n(e.key));
}
@ -558,7 +563,8 @@ export default class PopupPayment extends PopupElement { @@ -558,7 +563,8 @@ export default class PopupPayment extends PopupElement {
const postAddress = shippingAddress.shipping_address;
setRowTitle(shippingAddressRow, [postAddress.city, postAddress.street_line1, postAddress.street_line2].filter(Boolean).join(', '));
shippingMethodRow.container.classList.remove('hide');
shippingMethodRow.container.classList.toggle('hide', !lastRequestedInfo && !isReceipt);
} : undefined;
const setShippingInfo = (info: PaymentRequestedInfo) => {
@ -601,7 +607,13 @@ export default class PopupPayment extends PopupElement { @@ -601,7 +607,13 @@ export default class PopupPayment extends PopupElement {
shippingAmount = accumulate(shippingOption.prices.map(({amount}) => +amount), 0);
lastShippingPricesElements = makePricesElements(shippingOption.prices);
let l = totalLabel.label;
if(canTip) l = l.previousElementSibling.previousElementSibling as any;
if(canTip) {
l = l.previousElementSibling as any;
if(!isReceipt) {
l = l.previousElementSibling as any;
}
}
lastShippingPricesElements.forEach((element) => l.parentElement.insertBefore(element, l));
setTotal();
@ -617,7 +629,7 @@ export default class PopupPayment extends PopupElement { @@ -617,7 +629,7 @@ export default class PopupPayment extends PopupElement {
let lastShippingPricesElements: HTMLElement[];
shippingMethodRow = createRow({
icon: 'car',
icon: 'shipping',
titleLangKey: 'PaymentCheckoutShippingMethod',
clickable: !isReceipt && (onShippingMethodClick = () => {
new PopupPaymentShippingMethods(paymentForm as PaymentsPaymentForm, lastRequestedInfo, lastShippingOption).addEventListener('finish', (shippingOption) => {
@ -762,6 +774,11 @@ export default class PopupPayment extends PopupElement { @@ -762,6 +774,11 @@ export default class PopupPayment extends PopupElement {
onConfirmed();
} else {
popupPaymentVerification = new PopupPaymentVerification(paymentResult.url);
popupPaymentVerification.addEventListener('finish', () => {
popupPaymentVerification = undefined;
onConfirmed();
});
await new Promise<void>((resolve, reject) => {
popupPaymentVerification.addEventListener('close', () => {
popupPaymentVerification = undefined;
@ -774,11 +791,6 @@ export default class PopupPayment extends PopupElement { @@ -774,11 +791,6 @@ export default class PopupPayment extends PopupElement {
}
});
});
popupPaymentVerification.addEventListener('finish', () => {
popupPaymentVerification = undefined;
onConfirmed();
});
}
} catch(err) {
if((err as ApiError).type === 'BOT_PRECHECKOUT_TIMEOUT') {

1
src/components/popups/paymentCard.ts

@ -254,6 +254,7 @@ export default class PopupPaymentCard extends PopupElement<{ @@ -254,6 +254,7 @@ export default class PopupPaymentCard extends PopupElement<{
}
});
// putPreloader(this.body, true);
this.body.append(iframe);
this.show();
}

2
src/components/popups/reactedList.ts

@ -185,7 +185,7 @@ export default class PopupReactedList extends PopupElement { @@ -185,7 +185,7 @@ export default class PopupReactedList extends PopupElement {
const loader = loaders.get(tabContent);
loader.load();
});
}, undefined, undefined, undefined, this.listenerSetter);
// selectTab(hasAllReactions && hasReadParticipants ? 1 : 0, false);
selectTab(0, false);

18
src/components/row.ts

@ -12,6 +12,8 @@ import RadioForm from './radioForm'; @@ -12,6 +12,8 @@ import RadioForm from './radioForm';
import {i18n, LangPackKey} from '../lib/langPack';
import replaceContent from '../helpers/dom/replaceContent';
import setInnerHTML from '../helpers/dom/setInnerHTML';
import {attachClickEvent} from '../helpers/dom/clickEvent';
import ListenerSetter from '../helpers/listenerSetter';
export default class Row {
public container: HTMLElement;
@ -40,7 +42,9 @@ export default class Row { @@ -40,7 +42,9 @@ export default class Row {
clickable: boolean | ((e: Event) => void),
navigationTab: SliderSuperTab,
havePadding: boolean,
noRipple: boolean
noRipple: boolean,
noWrap: boolean,
listenerSetter: ListenerSetter
}> = {}) {
this.container = document.createElement(options.radioField || options.checkboxField ? 'label' : 'div');
this.container.classList.add('row');
@ -80,9 +84,12 @@ export default class Row { @@ -80,9 +84,12 @@ export default class Row {
}
if(!options.noCheckboxSubtitle && !isToggle) {
this.checkboxField.input.addEventListener('change', () => {
const onChange = () => {
replaceContent(this.subtitle, i18n(this.checkboxField.input.checked ? 'Checkbox.Enabled' : 'Checkbox.Disabled'));
});
};
if(options.listenerSetter) options.listenerSetter.add(this.checkboxField.input)('change', onChange);
else this.checkboxField.input.addEventListener('change', onChange);
}
}
@ -104,6 +111,7 @@ export default class Row { @@ -104,6 +111,7 @@ export default class Row {
this.title = document.createElement('div');
this.title.classList.add('row-title');
this.title.setAttribute('dir', 'auto');
if(options.noWrap) this.title.classList.add('no-wrap');
if(options.title) {
if(typeof(options.title) === 'string') {
this.title.innerHTML = options.title;
@ -149,10 +157,10 @@ export default class Row { @@ -149,10 +157,10 @@ export default class Row {
if(options.clickable || options.radioField || options.checkboxField) {
if(typeof(options.clickable) === 'function') {
this.container.addEventListener('click', (e) => {
attachClickEvent(this.container, (e) => {
if(this.freezed) return;
(options.clickable as any)(e);
});
}, {listenerSetter: options.listenerSetter});
}
this.container.classList.add('row-clickable', 'hover-effect');

16
src/components/scrollable.ts

@ -143,6 +143,13 @@ export class ScrollableBase { @@ -143,6 +143,13 @@ export class ScrollableBase {
this.removeHeavyAnimationListener = undefined;
}
public destroy() {
this.removeListeners();
this.onAdditionalScroll = undefined;
this.onScrolledTop = undefined;
this.onScrolledBottom = undefined;
}
public append(element: HTMLElement) {
this.container.append(element);
}
@ -172,7 +179,8 @@ export class ScrollableBase { @@ -172,7 +179,8 @@ export class ScrollableBase {
if((!this.onScrolledTop && !this.onScrolledBottom) && !this.splitUp && !this.onAdditionalScroll) return;
if(this.onScrollMeasure) return;
// if(this.onScrollMeasure) window.cancelAnimationFrame(this.onScrollMeasure);
this.onScrollMeasure = window.requestAnimationFrame(() => {
// this.onScrollMeasure = window.requestAnimationFrame(() => {
this.onScrollMeasure = window.setTimeout(() => {
this.onScrollMeasure = 0;
const scrollPosition = this.container[this.scrollProperty];
@ -187,12 +195,14 @@ export class ScrollableBase { @@ -187,12 +195,14 @@ export class ScrollableBase {
if(this.checkForTriggers) {
this.checkForTriggers();
}
});
// });
}, 200);
};
public cancelMeasure() {
if(this.onScrollMeasure) {
window.cancelAnimationFrame(this.onScrollMeasure);
// window.cancelAnimationFrame(this.onScrollMeasure);
clearTimeout(this.onScrollMeasure);
this.onScrollMeasure = 0;
}
}

6
src/components/sidebarLeft/index.ts

@ -34,7 +34,7 @@ import App from '../../config/app'; @@ -34,7 +34,7 @@ import App from '../../config/app';
import ButtonMenuToggle from '../buttonMenuToggle';
import replaceContent from '../../helpers/dom/replaceContent';
import sessionStorage from '../../lib/sessionStorage';
import {attachClickEvent, CLICK_EVENT_NAME} from '../../helpers/dom/clickEvent';
import {attachClickEvent, CLICK_EVENT_NAME, simulateClickEvent} from '../../helpers/dom/clickEvent';
import ButtonIcon from '../buttonIcon';
import confirmationPopup from '../confirmationPopup';
import IS_GEOLOCATION_SUPPORTED from '../../environment/geolocationSupport';
@ -359,7 +359,7 @@ export class AppSidebarLeft extends SidebarSlider { @@ -359,7 +359,7 @@ export class AppSidebarLeft extends SidebarSlider {
const close = () => {
// setTimeout(() => {
this.backBtn.click();
simulateClickEvent(this.backBtn);
// }, 0);
};
@ -632,7 +632,7 @@ export class AppSidebarLeft extends SidebarSlider { @@ -632,7 +632,7 @@ export class AppSidebarLeft extends SidebarSlider {
this.inputSearch.input.addEventListener('focus', onFocus);
onFocus();
this.backBtn.addEventListener('click', (e) => {
attachClickEvent(this.backBtn, (e) => {
this.toolsBtn.classList.add(activeClassName);
this.backBtn.classList.remove(activeClassName);
this.toolsBtn.parentElement.firstElementChild.classList.toggle('state-back', false);

2
src/components/sidebarLeft/tabs/autoDownload/file.ts

@ -21,7 +21,7 @@ export default class AppAutoDownloadFileTab extends SliderSuperTabEventable { @@ -21,7 +21,7 @@ export default class AppAutoDownloadFileTab extends SliderSuperTabEventable {
this.managers.appStateManager.setByKey('settings.autoDownloadNew.file_size_max', sizeMax);
}, 200, false, true);
const section = autoDownloadPeerTypeSection('file', 'AutoDownloadFilesTitle');
const section = autoDownloadPeerTypeSection('file', 'AutoDownloadFilesTitle', this.listenerSetter);
const MIN = 512 * 1024;
// const MAX = 2 * 1024 * 1024 * 1024;

17
src/components/sidebarLeft/tabs/autoDownload/photo.ts

@ -4,12 +4,13 @@ @@ -4,12 +4,13 @@
* https://github.com/morethanwords/tweb/blob/master/LICENSE
*/
import type ListenerSetter from '../../../../helpers/listenerSetter';
import {SettingSection} from '../..';
import {LangPackKey} from '../../../../lib/langPack';
import CheckboxField from '../../../checkboxField';
import {SliderSuperTabEventable} from '../../../sliderTab';
export function autoDownloadPeerTypeSection(type: 'photo' | 'video' | 'file', title: LangPackKey) {
export function autoDownloadPeerTypeSection(type: 'photo' | 'video' | 'file', title: LangPackKey, listenerSetter: ListenerSetter) {
const section = new SettingSection({name: title});
const key = 'settings.autoDownload.' + type + '.';
@ -17,25 +18,29 @@ export function autoDownloadPeerTypeSection(type: 'photo' | 'video' | 'file', ti @@ -17,25 +18,29 @@ export function autoDownloadPeerTypeSection(type: 'photo' | 'video' | 'file', ti
text: 'AutodownloadContacts',
name: 'contacts',
stateKey: key + 'contacts',
withRipple: true
withRipple: true,
listenerSetter
});
const privateCheckboxField = new CheckboxField({
text: 'AutodownloadPrivateChats',
name: 'private',
stateKey: key + 'private',
withRipple: true
withRipple: true,
listenerSetter
});
const groupsCheckboxField = new CheckboxField({
text: 'AutodownloadGroupChats',
name: 'groups',
stateKey: key + 'groups',
withRipple: true
withRipple: true,
listenerSetter
});
const channelsCheckboxField = new CheckboxField({
text: 'AutodownloadChannels',
name: 'channels',
stateKey: key + 'channels',
withRipple: true
withRipple: true,
listenerSetter
});
section.content.append(
@ -53,7 +58,7 @@ export default class AppAutoDownloadPhotoTab extends SliderSuperTabEventable { @@ -53,7 +58,7 @@ export default class AppAutoDownloadPhotoTab extends SliderSuperTabEventable {
this.header.classList.add('with-border');
this.setTitle('AutoDownloadPhotos');
const section = autoDownloadPeerTypeSection('photo', 'AutoDownloadPhotosTitle');
const section = autoDownloadPeerTypeSection('photo', 'AutoDownloadPhotosTitle', this.listenerSetter);
this.scrollable.append(section.container);
}
}

2
src/components/sidebarLeft/tabs/autoDownload/video.ts

@ -12,7 +12,7 @@ export default class AppAutoDownloadVideoTab extends SliderSuperTabEventable { @@ -12,7 +12,7 @@ export default class AppAutoDownloadVideoTab extends SliderSuperTabEventable {
this.header.classList.add('with-border');
this.setTitle('AutoDownloadVideos');
const section = autoDownloadPeerTypeSection('video', 'AutoDownloadVideosTitle');
const section = autoDownloadPeerTypeSection('video', 'AutoDownloadVideosTitle', this.listenerSetter);
this.scrollable.append(section.container);
}
}

15
src/components/sidebarLeft/tabs/dataAndStorage.ts

@ -75,7 +75,8 @@ export default class AppDataAndStorageTab extends SliderSuperTabEventable { @@ -75,7 +75,8 @@ export default class AppDataAndStorageTab extends SliderSuperTabEventable {
subtitle: '',
clickable: () => {
openTab(AppAutoDownloadPhotoTab);
}
},
listenerSetter: this.listenerSetter
});
const videoRow = new Row({
@ -83,7 +84,8 @@ export default class AppDataAndStorageTab extends SliderSuperTabEventable { @@ -83,7 +84,8 @@ export default class AppDataAndStorageTab extends SliderSuperTabEventable {
subtitle: '',
clickable: () => {
openTab(AppAutoDownloadVideoTab);
}
},
listenerSetter: this.listenerSetter
});
const fileRow = new Row({
@ -91,7 +93,8 @@ export default class AppDataAndStorageTab extends SliderSuperTabEventable { @@ -91,7 +93,8 @@ export default class AppDataAndStorageTab extends SliderSuperTabEventable {
subtitle: '',
clickable: () => {
openTab(AppAutoDownloadFileTab);
}
},
listenerSetter: this.listenerSetter
});
const resetButton = Button('btn-primary btn-transparent primary', {icon: 'delete', text: 'ResetAutomaticMediaDownload'});
@ -154,13 +157,15 @@ export default class AppDataAndStorageTab extends SliderSuperTabEventable { @@ -154,13 +157,15 @@ export default class AppDataAndStorageTab extends SliderSuperTabEventable {
text: 'AutoplayGIF',
name: 'gifs',
stateKey: 'settings.autoPlay.gifs',
withRipple: true
withRipple: true,
listenerSetter: this.listenerSetter
});
const videosCheckboxField = new CheckboxField({
text: 'AutoplayVideo',
name: 'videos',
stateKey: 'settings.autoPlay.videos',
withRipple: true
withRipple: true,
listenerSetter: this.listenerSetter
});
section.content.append(gifsCheckboxField.label, videosCheckboxField.label);

19
src/components/sidebarLeft/tabs/editFolder.ts

@ -24,6 +24,7 @@ import deepEqual from '../../../helpers/object/deepEqual'; @@ -24,6 +24,7 @@ import deepEqual from '../../../helpers/object/deepEqual';
import documentFragmentToHTML from '../../../helpers/dom/documentFragmentToHTML';
import wrapDraftText from '../../../lib/richTextProcessor/wrapDraftText';
import filterAsync from '../../../helpers/array/filterAsync';
import {attachClickEvent} from '../../../helpers/dom/clickEvent';
const MAX_FOLDER_NAME_LENGTH = 12;
@ -79,7 +80,7 @@ export default class AppEditFolderTab extends SliderSuperTab { @@ -79,7 +80,7 @@ export default class AppEditFolderTab extends SliderSuperTab {
}).show();
}
};
this.menuBtn = ButtonMenuToggle({}, 'bottom-left', [deleteFolderButton]);
this.menuBtn = ButtonMenuToggle({listenerSetter: this.listenerSetter}, 'bottom-left', [deleteFolderButton]);
this.menuBtn.classList.add('hide');
this.header.append(this.confirmBtn, this.menuBtn);
@ -174,15 +175,15 @@ export default class AppEditFolderTab extends SliderSuperTab { @@ -174,15 +175,15 @@ export default class AppEditFolderTab extends SliderSuperTab {
const includedFlagsContainer = this.includePeerIds.container.querySelector('.folder-categories');
const excludedFlagsContainer = this.excludePeerIds.container.querySelector('.folder-categories');
includedFlagsContainer.querySelector('.btn').addEventListener('click', () => {
attachClickEvent(includedFlagsContainer.querySelector('.btn') as HTMLElement, () => {
this.slider.createTab(AppIncludedChatsTab).open(this.filter, 'included', this);
});
}, {listenerSetter: this.listenerSetter});
excludedFlagsContainer.querySelector('.btn').addEventListener('click', () => {
attachClickEvent(excludedFlagsContainer.querySelector('.btn') as HTMLElement, () => {
this.slider.createTab(AppIncludedChatsTab).open(this.filter, 'excluded', this);
});
}, {listenerSetter: this.listenerSetter});
this.confirmBtn.addEventListener('click', () => {
attachClickEvent(this.confirmBtn, () => {
if(this.nameInputField.input.classList.contains('error')) {
return;
}
@ -222,9 +223,9 @@ export default class AppEditFolderTab extends SliderSuperTab { @@ -222,9 +223,9 @@ export default class AppEditFolderTab extends SliderSuperTab {
}).finally(() => {
this.confirmBtn.removeAttribute('disabled');
});
});
}, {listenerSetter: this.listenerSetter});
this.nameInputField.input.addEventListener('input', () => {
this.listenerSetter.add(this.nameInputField.input)('input', () => {
this.filter.title = this.nameInputField.value;
this.editCheckForChange();
});
@ -336,7 +337,7 @@ export default class AppEditFolderTab extends SliderSuperTab { @@ -336,7 +337,7 @@ export default class AppEditFolderTab extends SliderSuperTab {
const content = section.generateContentElement();
showMore = Button('folder-category-button btn btn-primary btn-transparent', {icon: 'down'});
showMore.classList.add('load-more', 'rp-overflow');
showMore.addEventListener('click', () => renderMore(20));
attachClickEvent(showMore, () => renderMore(20), {listenerSetter: this.listenerSetter});
showMore.append(i18n('FilterShowMoreChats', [peers.length]));
content.append(showMore);

21
src/components/sidebarLeft/tabs/generalSettings.ts

@ -111,7 +111,8 @@ export default class AppGeneralSettingsTab extends SliderSuperTabEventable { @@ -111,7 +111,8 @@ export default class AppGeneralSettingsTab extends SliderSuperTabEventable {
text: 'EnableAnimations',
name: 'animations',
stateKey: 'settings.animationsEnabled',
withRipple: true
withRipple: true,
listenerSetter: this.listenerSetter
});
container.append(range.container, chatBackgroundButton, animationsCheckboxField.label);
@ -231,13 +232,15 @@ export default class AppGeneralSettingsTab extends SliderSuperTabEventable { @@ -231,13 +232,15 @@ export default class AppGeneralSettingsTab extends SliderSuperTabEventable {
text: 'GeneralSettings.EmojiPrediction',
name: 'suggest-emoji',
stateKey: 'settings.emoji.suggest',
withRipple: true
withRipple: true,
listenerSetter: this.listenerSetter
});
const bigCheckboxField = new CheckboxField({
text: 'GeneralSettings.BigEmoji',
name: 'emoji-big',
stateKey: 'settings.emoji.big',
withRipple: true
withRipple: true,
listenerSetter: this.listenerSetter
});
container.append(suggestCheckboxField.label, bigCheckboxField.label);
@ -251,7 +254,8 @@ export default class AppGeneralSettingsTab extends SliderSuperTabEventable { @@ -251,7 +254,8 @@ export default class AppGeneralSettingsTab extends SliderSuperTabEventable {
havePadding: true,
clickable: () => {
this.slider.createTab(AppQuickReactionTab).open();
}
},
listenerSetter: this.listenerSetter
});
const renderQuickReaction = () => {
@ -272,13 +276,15 @@ export default class AppGeneralSettingsTab extends SliderSuperTabEventable { @@ -272,13 +276,15 @@ export default class AppGeneralSettingsTab extends SliderSuperTabEventable {
text: 'Stickers.SuggestStickers',
name: 'suggest',
stateKey: 'settings.stickers.suggest',
withRipple: true
withRipple: true,
listenerSetter: this.listenerSetter
});
const loopCheckboxField = new CheckboxField({
text: 'InstalledStickers.LoopAnimated',
name: 'loop',
stateKey: 'settings.stickers.loop',
withRipple: true
withRipple: true,
listenerSetter: this.listenerSetter
});
const stickerSets: {[id: string]: Row} = {};
@ -294,7 +300,8 @@ export default class AppGeneralSettingsTab extends SliderSuperTabEventable { @@ -294,7 +300,8 @@ export default class AppGeneralSettingsTab extends SliderSuperTabEventable {
havePadding: true,
clickable: () => {
new PopupStickers({id: stickerSet.id, access_hash: stickerSet.access_hash}).show();
}
},
listenerSetter: this.listenerSetter
});
stickerSets[stickerSet.id] = row;

7
src/components/sidebarLeft/tabs/includedChats.ts

@ -22,6 +22,7 @@ import wrapEmojiText from '../../../lib/richTextProcessor/wrapEmojiText'; @@ -22,6 +22,7 @@ import wrapEmojiText from '../../../lib/richTextProcessor/wrapEmojiText';
import {REAL_FOLDERS} from '../../../lib/mtproto/mtproto_config';
import rootScope from '../../../lib/rootScope';
import {MTAppConfig} from '../../../lib/mtproto/appConfig';
import {attachClickEvent, simulateClickEvent} from '../../../helpers/dom/clickEvent';
export default class AppIncludedChatsTab extends SliderSuperTab {
private editFolderTab: AppEditFolderTab;
@ -43,7 +44,7 @@ export default class AppIncludedChatsTab extends SliderSuperTab { @@ -43,7 +44,7 @@ export default class AppIncludedChatsTab extends SliderSuperTab {
this.header.append(this.confirmBtn);
this.confirmBtn.addEventListener('click', async() => {
attachClickEvent(this.confirmBtn, async() => {
const selected = this.selector.getSelected();
// this.filter.pFlags = {};
@ -107,7 +108,7 @@ export default class AppIncludedChatsTab extends SliderSuperTab { @@ -107,7 +108,7 @@ export default class AppIncludedChatsTab extends SliderSuperTab {
this.editFolderTab.setFilter(this.filter, false);
this.close();
});
}, {listenerSetter: this.listenerSetter});
const onAppConfig = (appConfig: MTAppConfig) => {
this.limit = rootScope.premium ? appConfig.dialog_filters_chats_limit_premium : appConfig.dialog_filters_chats_limit_default;
@ -266,7 +267,7 @@ export default class AppIncludedChatsTab extends SliderSuperTab { @@ -266,7 +267,7 @@ export default class AppIncludedChatsTab extends SliderSuperTab {
for(const flag in filter.pFlags) {
// @ts-ignore
if(details.hasOwnProperty(flag) && !!filter.pFlags[flag]) {
(categoriesSection.content.querySelector(`[data-peer-id="${flag}"]`) as HTMLElement).click();
simulateClickEvent(categoriesSection.content.querySelector(`[data-peer-id="${flag}"]`) as HTMLElement);
}
}
}

21
src/components/sidebarLeft/tabs/language.ts

@ -21,11 +21,22 @@ export default class AppLanguageTab extends SliderSuperTab { @@ -21,11 +21,22 @@ export default class AppLanguageTab extends SliderSuperTab {
const radioRows: Map<string, Row> = new Map();
const promise = this.managers.apiManager.invokeApiCacheable('langpack.getLanguages', {
lang_pack: 'macos'
}).then((languages) => {
const promise = Promise.all([
this.managers.apiManager.invokeApiCacheable('langpack.getLanguages', {
lang_pack: 'web'
}),
this.managers.apiManager.invokeApiCacheable('langpack.getLanguages', {
lang_pack: 'macos'
}),
]).then(([languages1, languages2]) => {
const rendered: Set<string> = new Set();
const webLangCodes = languages1.map((language) => language.lang_code);
const random = randomLong();
languages.forEach((language) => {
languages1.concat(languages2).forEach((language) => {
if(rendered.has(language.lang_code)) return;
rendered.add(language.lang_code);
const row = new Row({
radioField: new RadioField({
text: language.name,
@ -39,7 +50,7 @@ export default class AppLanguageTab extends SliderSuperTab { @@ -39,7 +50,7 @@ export default class AppLanguageTab extends SliderSuperTab {
});
const form = RadioFormFromRows([...radioRows.values()], (value) => {
I18n.getLangPack(value);
I18n.getLangPack(value, webLangCodes.includes(value));
});
I18n.getCacheLangPack().then((langPack) => {

4
src/components/sidebarLeft/tabs/newChannel.ts

@ -60,7 +60,7 @@ export default class AppNewChannelTab extends SliderSuperTab { @@ -60,7 +60,7 @@ export default class AppNewChannelTab extends SliderSuperTab {
this.nextBtn = ButtonCorner({icon: 'arrow_next'});
this.nextBtn.addEventListener('click', () => {
attachClickEvent(this.nextBtn, () => {
const title = this.channelNameInputField.value;
const about = this.channelDescriptionInputField.value;
@ -89,7 +89,7 @@ export default class AppNewChannelTab extends SliderSuperTab { @@ -89,7 +89,7 @@ export default class AppNewChannelTab extends SliderSuperTab {
}
});
});
});
}, {listenerSetter: this.listenerSetter});
this.content.append(this.nextBtn);
section.content.append(this.avatarEdit.container, inputWrapper);

6
src/components/sidebarLeft/tabs/newGroup.ts

@ -68,7 +68,7 @@ export default class AppNewGroupTab extends SliderSuperTab { @@ -68,7 +68,7 @@ export default class AppNewGroupTab extends SliderSuperTab {
this.groupLocationInputField.container
);
this.groupNameInputField.input.addEventListener('input', () => {
this.listenerSetter.add(this.groupNameInputField.input)('input', () => {
const value = this.groupNameInputField.value;
let valueCheck = !!value.length && !this.groupNameInputField.input.classList.contains('error');
if(this.isGeoChat) valueCheck = valueCheck && !!this.userLocationCoords && !!this.userLocationAddress;
@ -77,7 +77,7 @@ export default class AppNewGroupTab extends SliderSuperTab { @@ -77,7 +77,7 @@ export default class AppNewGroupTab extends SliderSuperTab {
this.nextBtn = ButtonCorner({icon: 'arrow_next'});
this.nextBtn.addEventListener('click', () => {
attachClickEvent(this.nextBtn, () => {
const title = this.groupNameInputField.value;
let promise: Promise<ChatId>;
@ -128,7 +128,7 @@ export default class AppNewGroupTab extends SliderSuperTab { @@ -128,7 +128,7 @@ export default class AppNewGroupTab extends SliderSuperTab {
appImManager.setInnerPeer({peerId: chatId.toPeerId(true)});
});
});
}, {listenerSetter: this.listenerSetter});
const chatsSection = new SettingSection({
name: 'Members',

14
src/components/sidebarLeft/tabs/notifications.ts

@ -35,12 +35,14 @@ export default class AppNotificationsTab extends SliderSuperTabEventable { @@ -35,12 +35,14 @@ export default class AppNotificationsTab extends SliderSuperTabEventable {
const enabledRow = new Row({
checkboxField: new CheckboxField({text: options.typeText, checked: true}),
subtitleLangKey: 'Loading'
subtitleLangKey: 'Loading',
listenerSetter: this.listenerSetter
});
const previewEnabledRow = new Row({
checkboxField: new CheckboxField({text: 'MessagePreview', checked: true}),
subtitleLangKey: 'Loading'
subtitleLangKey: 'Loading',
listenerSetter: this.listenerSetter
});
section.content.append(enabledRow.container, previewEnabledRow.container);
@ -111,12 +113,14 @@ export default class AppNotificationsTab extends SliderSuperTabEventable { @@ -111,12 +113,14 @@ export default class AppNotificationsTab extends SliderSuperTabEventable {
const contactsSignUpRow = new Row({
checkboxField: new CheckboxField({text: 'ContactJoined', checked: true}),
subtitleLangKey: 'Loading'
subtitleLangKey: 'Loading',
listenerSetter: this.listenerSetter
});
const soundRow = new Row({
checkboxField: new CheckboxField({text: 'Notifications.Sound', checked: true, stateKey: 'settings.notifications.sound'}),
subtitleLangKey: 'Loading'
checkboxField: new CheckboxField({text: 'Notifications.Sound', checked: true, stateKey: 'settings.notifications.sound', listenerSetter: this.listenerSetter}),
subtitleLangKey: 'Loading',
listenerSetter: this.listenerSetter
});
apiManagerProxy.getState().then((state) => {

27
src/components/sidebarLeft/tabs/privacyAndSecurity.ts

@ -56,7 +56,8 @@ export default class AppPrivacyAndSecurityTab extends SliderSuperTabEventable { @@ -56,7 +56,8 @@ export default class AppPrivacyAndSecurityTab extends SliderSuperTabEventable {
const tab = this.slider.createTab(AppBlockedUsersTab);
tab.peerIds = blockedPeerIds;
tab.open();
}
},
listenerSetter: this.listenerSetter
});
blockedUsersRow.freezed = true;
@ -81,7 +82,8 @@ export default class AppPrivacyAndSecurityTab extends SliderSuperTabEventable { @@ -81,7 +82,8 @@ export default class AppPrivacyAndSecurityTab extends SliderSuperTabEventable {
tab.state = passwordState;
tab.open();
}
},
listenerSetter: this.listenerSetter
};
const twoFactorRow = new Row(twoFactorRowOptions);
@ -98,7 +100,8 @@ export default class AppPrivacyAndSecurityTab extends SliderSuperTabEventable { @@ -98,7 +100,8 @@ export default class AppPrivacyAndSecurityTab extends SliderSuperTabEventable {
this.updateActiveSessions();
}, {once: true});
tab.open();
}
},
listenerSetter: this.listenerSetter
});
activeSessionsRow.freezed = true;
@ -157,7 +160,8 @@ export default class AppPrivacyAndSecurityTab extends SliderSuperTabEventable { @@ -157,7 +160,8 @@ export default class AppPrivacyAndSecurityTab extends SliderSuperTabEventable {
subtitleLangKey: SUBTITLE,
clickable: () => {
this.slider.createTab(AppPrivacyPhoneNumberTab).open();
}
},
listenerSetter: this.listenerSetter
});
const lastSeenTimeRow = rowsByKeys['inputPrivacyKeyStatusTimestamp'] = new Row({
@ -165,7 +169,8 @@ export default class AppPrivacyAndSecurityTab extends SliderSuperTabEventable { @@ -165,7 +169,8 @@ export default class AppPrivacyAndSecurityTab extends SliderSuperTabEventable {
subtitleLangKey: SUBTITLE,
clickable: () => {
this.slider.createTab(AppPrivacyLastSeenTab).open();
}
},
listenerSetter: this.listenerSetter
});
const photoVisibilityRow = rowsByKeys['inputPrivacyKeyProfilePhoto'] = new Row({
@ -173,7 +178,8 @@ export default class AppPrivacyAndSecurityTab extends SliderSuperTabEventable { @@ -173,7 +178,8 @@ export default class AppPrivacyAndSecurityTab extends SliderSuperTabEventable {
subtitleLangKey: SUBTITLE,
clickable: () => {
this.slider.createTab(AppPrivacyProfilePhotoTab).open();
}
},
listenerSetter: this.listenerSetter
});
const callRow = rowsByKeys['inputPrivacyKeyPhoneCall'] = new Row({
@ -181,7 +187,8 @@ export default class AppPrivacyAndSecurityTab extends SliderSuperTabEventable { @@ -181,7 +187,8 @@ export default class AppPrivacyAndSecurityTab extends SliderSuperTabEventable {
subtitleLangKey: SUBTITLE,
clickable: () => {
this.slider.createTab(AppPrivacyCallsTab).open();
}
},
listenerSetter: this.listenerSetter
});
const linkAccountRow = rowsByKeys['inputPrivacyKeyForwards'] = new Row({
@ -189,7 +196,8 @@ export default class AppPrivacyAndSecurityTab extends SliderSuperTabEventable { @@ -189,7 +196,8 @@ export default class AppPrivacyAndSecurityTab extends SliderSuperTabEventable {
subtitleLangKey: SUBTITLE,
clickable: () => {
this.slider.createTab(AppPrivacyForwardMessagesTab).open();
}
},
listenerSetter: this.listenerSetter
});
const groupChatsAddRow = rowsByKeys['inputPrivacyKeyChatInvite'] = new Row({
@ -197,7 +205,8 @@ export default class AppPrivacyAndSecurityTab extends SliderSuperTabEventable { @@ -197,7 +205,8 @@ export default class AppPrivacyAndSecurityTab extends SliderSuperTabEventable {
subtitleLangKey: SUBTITLE,
clickable: () => {
this.slider.createTab(AppPrivacyAddToGroupsTab).open();
}
},
listenerSetter: this.listenerSetter
});
const updatePrivacyRow = (key: InputPrivacyKey['_']) => {

25
src/components/sidebarLeft/tabs/settings.ts

@ -27,6 +27,7 @@ import {SliderSuperTabConstructable} from '../../sliderTab'; @@ -27,6 +27,7 @@ import {SliderSuperTabConstructable} from '../../sliderTab';
import PopupAvatar from '../../popups/avatar';
import {AccountAuthorizations, Authorization} from '../../../layer';
import PopupElement from '../../popups';
import {attachClickEvent} from '../../../helpers/dom/clickEvent';
// import AppMediaViewer from "../../appMediaViewerNew";
export default class AppSettingsTab extends SliderSuperTab {
@ -50,7 +51,7 @@ export default class AppSettingsTab extends SliderSuperTab { @@ -50,7 +51,7 @@ export default class AppSettingsTab extends SliderSuperTab {
this.container.classList.add('settings-container');
this.setTitle('Settings');
const btnMenu = ButtonMenuToggle({}, 'bottom-left', [{
const btnMenu = ButtonMenuToggle({listenerSetter: this.listenerSetter}, 'bottom-left', [{
icon: 'logout',
text: 'EditAccount.Logout',
onClick: () => {
@ -78,14 +79,14 @@ export default class AppSettingsTab extends SliderSuperTab { @@ -78,14 +79,14 @@ export default class AppSettingsTab extends SliderSuperTab {
const fillPromise = this.profile.fillProfileElements();
const changeAvatarBtn = Button('btn-circle btn-corner z-depth-1 profile-change-avatar', {icon: 'cameraadd'});
changeAvatarBtn.addEventListener('click', () => {
attachClickEvent(changeAvatarBtn, () => {
const canvas = document.createElement('canvas');
PopupElement.createPopup(PopupAvatar).open(canvas, (upload) => {
upload().then((inputFile) => {
return this.managers.appProfileManager.uploadProfilePhoto(inputFile);
});
});
});
}, {listenerSetter: this.listenerSetter});
this.profile.element.lastElementChild.firstElementChild.append(changeAvatarBtn);
const updateChangeAvatarBtn = async() => {
@ -160,7 +161,8 @@ export default class AppSettingsTab extends SliderSuperTab { @@ -160,7 +161,8 @@ export default class AppSettingsTab extends SliderSuperTab {
clickable: () => {
this.slider.createTab(tabConstructor).open();
// new tabConstructor(this.slider, true).open();
}
},
listenerSetter: this.listenerSetter
});
});
@ -181,7 +183,8 @@ export default class AppSettingsTab extends SliderSuperTab { @@ -181,7 +183,8 @@ export default class AppSettingsTab extends SliderSuperTab {
this.updateActiveSessions(true);
}, {once: true});
tab.open();
}
},
listenerSetter: this.listenerSetter
}),
this.languageRow = new Row({
@ -190,7 +193,8 @@ export default class AppSettingsTab extends SliderSuperTab { @@ -190,7 +193,8 @@ export default class AppSettingsTab extends SliderSuperTab {
icon: 'language',
clickable: () => {
this.slider.createTab(AppLanguageTab).open();
}
},
listenerSetter: this.listenerSetter
})
);
@ -204,10 +208,10 @@ export default class AppSettingsTab extends SliderSuperTab { @@ -204,10 +208,10 @@ export default class AppSettingsTab extends SliderSuperTab {
this.scrollable.append(this.profile.element/* profileSection.container */, buttonsSection.container);
this.buttons.edit.addEventListener('click', () => {
attachClickEvent(this.buttons.edit, () => {
const tab = this.slider.createTab(AppEditProfileTab);
tab.open();
});
}, {listenerSetter: this.listenerSetter});
lottieLoader.loadLottieWorkers();
@ -235,4 +239,9 @@ export default class AppSettingsTab extends SliderSuperTab { @@ -235,4 +239,9 @@ export default class AppSettingsTab extends SliderSuperTab {
this.devicesRow.titleRight.textContent = '' + this.authorizations.length;
});
}
public onCloseAfterTimeout() {
this.profile.destroy();
return super.onCloseAfterTimeout();
}
}

16
src/components/sidebarRight/index.ts

@ -46,15 +46,19 @@ export class AppSidebarRight extends SidebarSlider { @@ -46,15 +46,19 @@ export class AppSidebarRight extends SidebarSlider {
return tab;
}
public replaceSharedMediaTab(tab: AppSharedMediaTab) {
public replaceSharedMediaTab(tab?: AppSharedMediaTab) {
const previousTab = this.sharedMediaTab;
if(previousTab) {
const wasActive = previousTab.container.classList.contains('active');
if(wasActive) {
tab.container.classList.add('active');
}
if(tab) {
const wasActive = previousTab.container.classList.contains('active');
if(wasActive) {
tab.container.classList.add('active');
}
previousTab.container.replaceWith(tab.container);
previousTab.container.replaceWith(tab.container);
} else {
previousTab.container.remove();
}
} else {
this.tabsContainer.prepend(tab.container);
}

6
src/components/sidebarRight/tabs/chatReactions.ts

@ -29,7 +29,8 @@ export default class AppChatReactionsTab extends SliderSuperTabEventable { @@ -29,7 +29,8 @@ export default class AppChatReactionsTab extends SliderSuperTabEventable {
const toggleCheckboxField = new CheckboxField({toggle: true, checked: !!enabledReactions.size});
const toggleRow = new Row({
checkboxField: toggleCheckboxField,
titleLangKey: 'EnableReactions'
titleLangKey: 'EnableReactions',
listenerSetter: this.listenerSetter
});
toggleSection.content.append(toggleRow.container);
@ -65,7 +66,8 @@ export default class AppChatReactionsTab extends SliderSuperTabEventable { @@ -65,7 +66,8 @@ export default class AppChatReactionsTab extends SliderSuperTabEventable {
const row = new Row({
checkboxField,
title: availableReaction.title,
havePadding: true
havePadding: true,
listenerSetter: this.listenerSetter
});
wrapStickerToRow({

3
src/components/sidebarRight/tabs/chatType.ts

@ -78,7 +78,8 @@ export default class AppChatTypeTab extends SliderSuperTabEventable { @@ -78,7 +78,8 @@ export default class AppChatTypeTab extends SliderSuperTabEventable {
clickable: () => {
copyTextToClipboard((this.chatFull.exported_invite as ExportedChatInvite.chatInviteExported).link);
toast(I18n.format('LinkCopied', true));
}
},
listenerSetter: this.listenerSetter
});
const btnRevoke = Button('btn-primary btn-transparent danger', {icon: 'delete', text: 'RevokeLink'});

9
src/components/sidebarRight/tabs/editChat.ts

@ -113,7 +113,8 @@ export default class AppEditChatTab extends SliderSuperTab { @@ -113,7 +113,8 @@ export default class AppEditChatTab extends SliderSuperTab {
this.listenerSetter.add(tab.eventListener)('destroy', setChatTypeSubtitle);
},
icon: 'lock'
icon: 'lock',
listenerSetter: this.listenerSetter
});
const setChatTypeSubtitle = () => {
@ -147,7 +148,8 @@ export default class AppEditChatTab extends SliderSuperTab { @@ -147,7 +148,8 @@ export default class AppEditChatTab extends SliderSuperTab {
this.listenerSetter.add(tab.eventListener)('destroy', setReactionsLength);
});
}
},
listenerSetter: this.listenerSetter
});
const availableReactions = await this.managers.appReactionsManager.getAvailableReactions();
@ -181,7 +183,8 @@ export default class AppEditChatTab extends SliderSuperTab { @@ -181,7 +183,8 @@ export default class AppEditChatTab extends SliderSuperTab {
tab.chatId = this.chatId;
tab.open();
},
icon: 'permissions'
icon: 'permissions',
listenerSetter: this.listenerSetter
});
const setPermissionsLength = async() => {

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

@ -117,7 +117,8 @@ export default class AppEditContactTab extends SliderSuperTab { @@ -117,7 +117,8 @@ export default class AppEditContactTab extends SliderSuperTab {
if(!isNew) {
const notificationsRow = new Row({
checkboxField: notificationsCheckboxField
checkboxField: notificationsCheckboxField,
listenerSetter: this.listenerSetter
});
const enabled = !(await this.managers.appNotificationsManager.isPeerLocalMuted(this.peerId, false));

3
src/components/sidebarRight/tabs/groupPermissions.ts

@ -185,7 +185,8 @@ export default class AppGroupPermissionsTab extends SliderSuperTabEventable { @@ -185,7 +185,8 @@ export default class AppGroupPermissionsTab extends SliderSuperTabEventable {
placeholder: 'ExceptionModal.Search.Placeholder',
peerId: -this.chatId
});
}
},
listenerSetter: this.listenerSetter
});
const openPermissions = async(peerId: PeerId) => {

11
src/components/sidebarRight/tabs/sharedMedia.ts

@ -85,7 +85,7 @@ export default class AppSharedMediaTab extends SliderSuperTab { @@ -85,7 +85,7 @@ export default class AppSharedMediaTab extends SliderSuperTab {
// * body
this.profile = new PeerProfile(this.managers, this.scrollable);
this.profile = new PeerProfile(this.managers, this.scrollable, this.listenerSetter);
this.profile.init();
this.scrollable.append(this.profile.element);
@ -124,7 +124,7 @@ export default class AppSharedMediaTab extends SliderSuperTab { @@ -124,7 +124,7 @@ export default class AppSharedMediaTab extends SliderSuperTab {
} else if(!this.scrollable.isHeavyAnimationInProgress) {
this.slider.onCloseBtnClick();
}
});
}, {listenerSetter: this.listenerSetter});
attachClickEvent(this.editBtn, (e) => {
let tab: AppEditChatTab | AppEditContactTab;
@ -143,7 +143,7 @@ export default class AppSharedMediaTab extends SliderSuperTab { @@ -143,7 +143,7 @@ export default class AppSharedMediaTab extends SliderSuperTab {
tab.open();
}
});
}, {listenerSetter: this.listenerSetter});
this.listenerSetter.add(rootScope)('contacts_update', (userId) => {
if(this.peerId === userId) {
@ -217,7 +217,7 @@ export default class AppSharedMediaTab extends SliderSuperTab { @@ -217,7 +217,7 @@ export default class AppSharedMediaTab extends SliderSuperTab {
const btnAddMembers = ButtonCorner({icon: 'addmember_filled'});
this.content.append(btnAddMembers);
btnAddMembers.addEventListener('click', async() => {
attachClickEvent(btnAddMembers, async() => {
const peerId = this.peerId;
const id = this.peerId.toChatId();
const isChannel = await this.managers.appChatsManager.isChannel(id);
@ -315,7 +315,7 @@ export default class AppSharedMediaTab extends SliderSuperTab { @@ -315,7 +315,7 @@ export default class AppSharedMediaTab extends SliderSuperTab {
}
});
}
});
}, {listenerSetter: this.listenerSetter});
// console.log('construct shared media time:', performance.now() - perf);
}
@ -458,5 +458,6 @@ export default class AppSharedMediaTab extends SliderSuperTab { @@ -458,5 +458,6 @@ export default class AppSharedMediaTab extends SliderSuperTab {
this.destroyable = true;
this.onCloseAfterTimeout();
this.profile.destroy();
this.searchSuper.destroy();
}
}

3
src/components/singleTransition.ts

@ -19,6 +19,9 @@ const SetTransition = ( @@ -19,6 +19,9 @@ const SetTransition = (
clearTimeout(+timeout);
}
// useRafs = undefined;
// duration = 0;
if(raf !== undefined) {
window.cancelAnimationFrame(+raf);
if(!useRafs) {

2
src/components/slider.ts

@ -152,7 +152,7 @@ export default class SidebarSlider { @@ -152,7 +152,7 @@ export default class SidebarSlider {
if(tab.onCloseAfterTimeout) {
setTimeout(() => {
tab.onCloseAfterTimeout();
}, TRANSITION_TIME);
}, TRANSITION_TIME + 30);
}
}
}

6
src/components/sliderTab.ts

@ -107,10 +107,8 @@ export default class SliderSuperTab implements SliderTab { @@ -107,10 +107,8 @@ export default class SliderSuperTab implements SliderTab {
if(this.destroyable) { // ! WARNING, пока что это будет работать только с самой последней внутренней вкладкой !
this.slider.tabs.delete(this);
this.container.remove();
}
if(this.listenerSetter) {
this.listenerSetter.removeAll();
this.scrollable.destroy();
this.listenerSetter?.removeAll();
}
}

28
src/components/transition.ts

@ -9,6 +9,7 @@ import deferredPromise, {CancellablePromise} from '../helpers/cancellablePromise @@ -9,6 +9,7 @@ import deferredPromise, {CancellablePromise} from '../helpers/cancellablePromise
import {dispatchHeavyAnimationEvent} from '../hooks/useHeavyAnimationCheck';
import whichChild from '../helpers/dom/whichChild';
import cancelEvent from '../helpers/dom/cancelEvent';
import ListenerSetter from '../helpers/listenerSetter';
function slideNavigation(tabContent: HTMLElement, prevTabContent: HTMLElement, toRight: boolean) {
const width = prevTabContent.getBoundingClientRect().width;
@ -84,7 +85,8 @@ export const TransitionSlider = ( @@ -84,7 +85,8 @@ export const TransitionSlider = (
type: 'tabs' | 'navigation' | 'zoom-fade' | 'slide-fade' | 'none'/* | 'counter' */,
transitionTime: number,
onTransitionEnd?: (id: number) => void,
isHeavy = true
isHeavy = true,
listenerSetter?: ListenerSetter
) => {
let animationFunction: TransitionFunction = null;
@ -101,7 +103,7 @@ export const TransitionSlider = ( @@ -101,7 +103,7 @@ export const TransitionSlider = (
content.dataset.animation = type;
return Transition(content, animationFunction, transitionTime, onTransitionEnd, isHeavy);
return Transition(content, animationFunction, transitionTime, onTransitionEnd, isHeavy, undefined, undefined, listenerSetter);
};
type TransitionFunction = (tabContent: HTMLElement, prevTabContent: HTMLElement, toRight: boolean) => void | (() => void);
@ -113,7 +115,8 @@ const Transition = ( @@ -113,7 +115,8 @@ const Transition = (
onTransitionEnd?: (id: number) => void,
isHeavy = true,
once = false,
withAnimationListener = true
withAnimationListener = true,
listenerSetter?: ListenerSetter
) => {
const onTransitionEndCallbacks: Map<HTMLElement, Function> = new Map();
let animationDeferred: CancellablePromise<void>;
@ -133,7 +136,7 @@ const Transition = ( @@ -133,7 +136,7 @@ const Transition = (
// console.log('Transition: transitionend', /* content, */ e, selectTab.prevId, performance.now() - animationStarted);
const callback = onTransitionEndCallbacks.get(e.target as HTMLElement);
if(callback) callback();
callback?.();
if(e.target !== from) {
return;
@ -153,14 +156,16 @@ const Transition = ( @@ -153,14 +156,16 @@ const Transition = (
content.classList.remove('animating', 'backwards', 'disable-hover');
if(once) {
content.removeEventListener(listenerName, onEndEvent/* , {capture: false} */);
if(listenerSetter) listenerSetter.removeManual(content, listenerName, onEndEvent);
else content.removeEventListener(listenerName, onEndEvent/* , {capture: false} */);
from = animationDeferred = undefined;
onTransitionEndCallbacks.clear();
}
};
// TODO: check for transition type (transform, etc) using by animationFunction
content.addEventListener(listenerName, onEndEvent/* , {passive: true, capture: false} */);
if(listenerSetter) listenerSetter.add(content)(listenerName, onEndEvent);
else content.addEventListener(listenerName, onEndEvent/* , {passive: true, capture: false} */);
}
function selectTab(id: number | HTMLElement, animate = true, overrideFrom?: typeof from) {
@ -196,9 +201,7 @@ const Transition = ( @@ -196,9 +201,7 @@ const Transition = (
if(from) from.classList.remove('active', 'to', 'from');
else if(to) { // fix instant opening back from closed slider (e.g. instant closening and opening right sidebar)
const callback = onTransitionEndCallbacks.get(to);
if(callback) {
callback();
}
callback?.();
}
if(to) {
@ -254,21 +257,24 @@ const Transition = ( @@ -254,21 +257,24 @@ const Transition = (
}
if(from/* && false */) {
let timeout: number;
const _from = from;
const callback = () => {
clearTimeout(timeout);
_from.classList.remove('active', 'from');
if(onTransitionEndCallback) {
onTransitionEndCallback();
onTransitionEndCallback?.();
}
onTransitionEndCallbacks.delete(_from);
};
if(to) {
timeout = window.setTimeout(callback, transitionTime + 100); // something happened to container
onTransitionEndCallbacks.set(_from, callback);
} else {
const timeout = window.setTimeout(callback, transitionTime);
timeout = window.setTimeout(callback, transitionTime);
onTransitionEndCallbacks.set(_from, () => {
clearTimeout(timeout);
onTransitionEndCallbacks.delete(_from);

19
src/components/visibilityIntersector.ts

@ -5,20 +5,21 @@ @@ -5,20 +5,21 @@
*/
type TargetType = HTMLElement;
export type OnVisibilityChange = (target: TargetType, visible: boolean) => void;
export type OnVisibilityChangeItem = {target: TargetType, visible: boolean, entry: IntersectionObserverEntry};
export type OnVisibilityChange = (item: OnVisibilityChangeItem) => void;
export default class VisibilityIntersector {
private observer: IntersectionObserver;
private items: Map<TargetType, boolean> = new Map();
private locked = false;
constructor(onVisibilityChange: OnVisibilityChange) {
constructor(onVisibilityChange: OnVisibilityChange, options?: IntersectionObserverInit) {
this.observer = new IntersectionObserver((entries) => {
if(this.locked) {
return;
}
const changed: {target: TargetType, visible: boolean}[] = [];
const changed: OnVisibilityChangeItem[] = [];
entries.forEach((entry) => {
const target = entry.target as TargetType;
@ -37,15 +38,19 @@ export default class VisibilityIntersector { @@ -37,15 +38,19 @@ export default class VisibilityIntersector {
return;
} */
changed[entry.isIntersecting ? 'unshift' : 'push']({target, visible: entry.isIntersecting});
const change: typeof changed[0] = {target, visible: entry.isIntersecting, entry};
// ! order will be incorrect so can't use it
// changed[entry.isIntersecting ? 'unshift' : 'push'](change);
changed.push(change);
// onVisibilityChange(target, entry.isIntersecting);
});
changed.forEach((smth) => {
onVisibilityChange(smth.target, smth.visible);
changed.forEach((item) => {
onVisibilityChange(item);
});
});
}, options);
}
public getVisible() {

5
src/components/wrappers/messageActionTextNewUnsafe.ts

@ -267,7 +267,10 @@ export default async function wrapMessageActionTextNewUnsafe(message: MyMessage, @@ -267,7 +267,10 @@ export default async function wrapMessageActionTextNewUnsafe(message: MyMessage,
managers.appMessagesManager.fetchMessageReplyTo(message);
} else {
langPackKey = isRecurringUsed ? 'Chat.Service.PaymentSentRecurringUsed' : (isRecurringInit ? 'Chat.Service.PaymentSentRecurringInit' : 'Chat.Service.PaymentSent1');
args.push(wrapLinkToMessage(invoiceMessage, plain));
args.push(wrapLinkToMessage(invoiceMessage, plain).then((el) => {
el.classList.add('is-receipt-link');
return el;
}));
}
}

9
src/components/wrappers/stickerSetThumb.ts

@ -11,6 +11,7 @@ import appDownloadManager from '../../lib/appManagers/appDownloadManager'; @@ -11,6 +11,7 @@ import appDownloadManager from '../../lib/appManagers/appDownloadManager';
import {AppManagers} from '../../lib/appManagers/managers';
import lottieLoader from '../../lib/rlottie/lottieLoader';
import rootScope from '../../lib/rootScope';
import animationIntersector from '../animationIntersector';
import LazyLoadQueue from '../lazyLoadQueue';
import wrapSticker from './sticker';
@ -62,6 +63,10 @@ export default async function wrapStickerSetThumb({set, lazyLoadQueue, container @@ -62,6 +63,10 @@ export default async function wrapStickerSetThumb({set, lazyLoadQueue, container
return promise.then((blob) => {
renderImageFromUrl(media, URL.createObjectURL(blob), () => {
container.append(media);
if(set.pFlags.videos) {
animationIntersector.addAnimation(media as HTMLVideoElement, group);
}
});
});
}
@ -79,7 +84,9 @@ export default async function wrapStickerSetThumb({set, lazyLoadQueue, container @@ -79,7 +84,9 @@ export default async function wrapStickerSetThumb({set, lazyLoadQueue, container
div: container,
group: group,
lazyLoadQueue,
managers
managers,
width,
height
}); // kostil
}
}

13
src/components/wrappers/video.ts

@ -15,7 +15,8 @@ import isInDOM from '../../helpers/dom/isInDOM'; @@ -15,7 +15,8 @@ import isInDOM from '../../helpers/dom/isInDOM';
import renderImageFromUrl from '../../helpers/dom/renderImageFromUrl';
import mediaSizes, {ScreenSize} from '../../helpers/mediaSizes';
import onMediaLoad from '../../helpers/onMediaLoad';
import throttleWithRaf from '../../helpers/schedulers/throttleWithRaf';
import {fastRaf} from '../../helpers/schedulers';
import throttle from '../../helpers/schedulers/throttle';
import sequentialDom from '../../helpers/sequentialDom';
import toHHMMSS from '../../helpers/string/toHHMMSS';
import {Message, PhotoSize} from '../../layer';
@ -245,7 +246,9 @@ export default async function wrapVideo({doc, container, message, boxWidth, boxH @@ -245,7 +246,9 @@ export default async function wrapVideo({doc, container, message, boxWidth, boxH
spanTime.innerText = toHHMMSS(globalVideo.duration - globalVideo.currentTime, false);
};
const throttledTimeUpdate = throttleWithRaf(onTimeUpdate);
const throttledTimeUpdate = throttle(() => {
fastRaf(onTimeUpdate);
}, 1000, false);
const onPlay = () => {
video.classList.add('hide');
@ -437,14 +440,16 @@ export default async function wrapVideo({doc, container, message, boxWidth, boxH @@ -437,14 +440,16 @@ export default async function wrapVideo({doc, container, message, boxWidth, boxH
if(doc.type === 'video') {
const onTimeUpdate = () => {
if(!video.videoWidth) {
if(!video.duration) {
return;
}
spanTime.innerText = toHHMMSS(video.duration - video.currentTime, false);
};
const throttledTimeUpdate = throttleWithRaf(onTimeUpdate);
const throttledTimeUpdate = throttle(() => {
fastRaf(onTimeUpdate);
}, 1e3, false);
video.addEventListener('timeupdate', throttledTimeUpdate);

5
src/helpers/bigInt/bigIntConstants.ts

@ -0,0 +1,5 @@ @@ -0,0 +1,5 @@
import bigInt from 'big-integer';
export const safeBigInt = bigInt(Number.MAX_SAFE_INTEGER);
export const ulongBigInt = bigInt(bigInt[2]).pow(64);
export const longBigInt = ulongBigInt.divide(bigInt[2]);

9
src/helpers/bigInt/bigIntConversion.ts

@ -1,4 +1,5 @@ @@ -1,4 +1,5 @@
import bigInt from 'big-integer';
import { longBigInt, ulongBigInt } from './bigIntConstants';
export function bigIntFromBytes(bytes: Uint8Array | number[], base = 256) {
return bigInt.fromArray(bytes instanceof Uint8Array ? [...bytes] : bytes, base);
@ -7,3 +8,11 @@ export function bigIntFromBytes(bytes: Uint8Array | number[], base = 256) { @@ -7,3 +8,11 @@ export function bigIntFromBytes(bytes: Uint8Array | number[], base = 256) {
export function bigIntToBytes(bigInt: bigInt.BigInteger) {
return new Uint8Array(bigInt.toArray(256).value);
}
export function bigIntToSigned(bigInt: bigInt.BigInteger) {
return bigInt.greater(longBigInt) ? bigInt.minus(ulongBigInt) : bigInt;
}
export function bigIntToUnsigned(bigInt: bigInt.BigInteger) {
return bigInt.isNegative() ? ulongBigInt.add(bigInt) : bigInt;
}

2
src/helpers/canvas/getTextWidth.ts

@ -16,7 +16,7 @@ export default function getTextWidth(text: string, font: string) { @@ -16,7 +16,7 @@ export default function getTextWidth(text: string, font: string) {
// const perf = performance.now();
if(!context) {
const canvas = document.createElement('canvas');
context = canvas.getContext('2d');
context = canvas.getContext('2d', {alpha: false});
context.font = font;
}

11
src/helpers/cards/cardBrands.ts

@ -3,14 +3,15 @@ import replaceNonNumber from '../string/replaceNonNumber'; @@ -3,14 +3,15 @@ import replaceNonNumber from '../string/replaceNonNumber';
const CARD_BRAND_REGEXP: {[brand: string]: RegExp} = {
visa: /^4/,
mastercard: /^(51|52|53|54|55|22|23|24|25|26|27)/,
mastercard: /^(51|52|53|54|55|222|23|24|25|26|27)/,
amex: /^(34|37)/,
discover: /^(60|64|65)/,
diners: /^(30|38|39)/,
diners14: /^(36)/,
jcb: /^(35)/,
unionpay: /^(62[0-6,8-9]|627[0-6,8-9]|6277[0-7,9]|62778[1-9]|81)/,
elo: /^(5067|509|636368|627780)/
elo: /^(5067|509|636368|627780)/,
mir: /^(220[0-4])/
};
// * taken from Stripe
@ -74,6 +75,12 @@ export const CARD_BRANDS: {[b: string]: { @@ -74,6 +75,12 @@ export const CARD_BRANDS: {[b: string]: {
cvcMaxLength: 3,
cvcMinLength: null
},
mir: {
minLength: 16,
maxLength: 16,
cvcMaxLength: 3,
cvcMinLength: null
},
unknown: {
minLength: 16,
maxLength: 16,

7
src/helpers/cards/validateCard.ts

@ -16,6 +16,7 @@ function makeValidationError(code?: string) { @@ -16,6 +16,7 @@ function makeValidationError(code?: string) {
} : null;
}
// Luhn algorithm
function validateCompleteCardNumber(card: string) {
const t = '0'.charCodeAt(0);
const n = card.length % 2;
@ -61,7 +62,11 @@ function getCardInfoByNumber(card: string) { @@ -61,7 +62,11 @@ function getCardInfoByNumber(card: string) {
}
function makeCardNumberError(str: string, length: number, ignoreIncomplete: boolean) {
return str.length >= length ? (validateCompleteCardNumber(str) ? null : makeValidationError('invalid')) : (ignoreIncomplete ? null : makeValidationError('incomplete'));
if(str.length >= length) {
return validateCompleteCardNumber(str) || detectCardBrand(str) === 'mir' ? null : makeValidationError('invalid');
}
return ignoreIncomplete ? null : makeValidationError('incomplete');
}
export function validateCardNumber(str: string, options: PatternValidationOptions = {}) {

7
src/helpers/dom/renderImageWithFadeIn.ts

@ -38,12 +38,11 @@ export default function renderImageWithFadeIn( @@ -38,12 +38,11 @@ export default function renderImageWithFadeIn(
image.addEventListener('animationend', () => {
sequentialDom.mutate(() => {
image.classList.remove('fade-in');
if(thumbImage) {
thumbImage.remove();
}
thumbImage?.remove();
});
}, {once: true});
} else {
thumbImage?.remove();
}
});
});

8
src/helpers/dropdownHover.ts

@ -48,7 +48,10 @@ export default class DropdownHover extends EventListenerBase<{ @@ -48,7 +48,10 @@ export default class DropdownHover extends EventListenerBase<{
listenerSetter.add(button)('mouseover', (e) => {
// console.log('onmouseover button');
if(firstTime) {
listenerSetter.add(button)('mouseout', this.onMouseOut);
listenerSetter.add(button)('mouseout', (e) => {
clearTimeout(this.displayTimeout);
this.onMouseOut(e);
});
firstTime = false;
}
@ -61,9 +64,8 @@ export default class DropdownHover extends EventListenerBase<{ @@ -61,9 +64,8 @@ export default class DropdownHover extends EventListenerBase<{
}
private onMouseOut = (e: MouseEvent) => {
if(KEEP_OPEN) return;
if(KEEP_OPEN || !this.isActive()) return;
clearTimeout(this.displayTimeout);
if(!this.isActive()) return;
const toElement = (e as any).toElement as Element;
if(toElement && findUpAsChild(toElement, this.element)) {

75
src/helpers/paymentsWrapCurrencyAmount.ts

@ -25,49 +25,58 @@ function number_format(number: any, decimals: any, dec_point: any, thousands_sep @@ -25,49 +25,58 @@ function number_format(number: any, decimals: any, dec_point: any, thousands_sep
return s.join(dec);
}
export default function paymentsWrapCurrencyAmount($amount: number | string, $currency: string, $skipSymbol?: boolean) {
$amount = +$amount;
export default function paymentsWrapCurrencyAmount(amount: number | string, currency: string, skipSymbol?: boolean) {
amount = +amount;
const $currency_data = Currencies[$currency]; // вытащить из json
if(!$currency_data) {
const isNegative = amount < 0;
const currencyData = Currencies[currency];
if(!currencyData) {
throw new Error('CURRENCY_WRAP_INVALID');
}
const $amount_exp = $amount / Math.pow(10, $currency_data['exp']);
const amountExp = amount / Math.pow(10, currencyData.exp);
let $decimals = $currency_data['exp'];
if($currency == 'IRR' &&
Math.floor($amount_exp) == $amount_exp) {
$decimals = 0; // у иранцев копейки почти всегда = 0 и не показываются в UI
let decimals = currencyData.exp;
if(currency == 'IRR' && Math.floor(amountExp) == amountExp) {
decimals = 0; // у иранцев копейки почти всегда = 0 и не показываются в UI
}
const $formatted = number_format($amount_exp, $decimals, $currency_data['decimal_sep'], $currency_data['thousands_sep']);
if($skipSymbol) {
return $formatted;
let formatted = number_format(amountExp, decimals, currencyData.decimal_sep, currencyData.thousands_sep);
if(skipSymbol) {
return formatted;
}
const $splitter = $currency_data['space_between'] ? ' ' : '';
let $formatted_intern: string;
if($currency_data['symbol_left']) {
$formatted_intern = $currency_data['symbol'] + $splitter + $formatted;
} else {
$formatted_intern = $formatted + $splitter + $currency_data['symbol'];
let symbol = currencyData.symbol;
if(isNegative && !currencyData.space_between && currencyData.symbol_left) {
symbol = '-' + symbol;
formatted = formatted.replace('-', '');
}
return $formatted_intern;
}
function paymentsGetCurrencyExp($currency: string) {
if($currency == 'CLF') {
return 4;
}
if(['BHD', 'IQD', 'JOD', 'KWD', 'LYD', 'OMR', 'TND'].includes($currency)) {
return 3;
}
if(['BIF', 'BYR', 'CLP', 'CVE', 'DJF', 'GNF', 'ISK', 'JPY', 'KMF', 'KRW', 'MGA', 'PYG', 'RWF', 'UGX', 'UYI', 'VND', 'VUV', 'XAF', 'XOF', 'XPF'].includes($currency)) {
return 0;
}
if($currency == 'MRO') {
return 1;
let out: string;
const splitter = currencyData.space_between ? ' ' : '';
if(currencyData.symbol_left) {
out = symbol + splitter + formatted;
} else {
out = formatted + splitter + symbol;
}
return 2;
return out;
}
(window as any).p = paymentsWrapCurrencyAmount;
// function paymentsGetCurrencyExp($currency: string) {
// if($currency == 'CLF') {
// return 4;
// }
// if(['BHD','IQD','JOD','KWD','LYD','OMR','TND'].includes($currency)) {
// return 3;
// }
// if(['BIF','BYR','CLP','CVE','DJF','GNF','ISK','JPY','KMF','KRW','MGA', 'PYG','RWF','UGX','UYI','VND','VUV','XAF','XOF','XPF'].includes($currency)) {
// return 0;
// }
// if($currency == 'MRO') {
// return 1;
// }
// return 2;
// }

8
src/helpers/searchListLoader.ts

@ -191,20 +191,20 @@ export default class SearchListLoader<Item extends {mid: number, peerId: PeerId} @@ -191,20 +191,20 @@ export default class SearchListLoader<Item extends {mid: number, peerId: PeerId}
}
}
public getPrevious() {
public getPrevious(withOtherSide?: boolean) {
let previous = this.previous;
if(this.otherSideLoader) {
if(this.otherSideLoader && withOtherSide) {
previous = previous.concat(this.otherSideLoader.previous);
}
return previous;
}
public getNext() {
public getNext(withOtherSide?: boolean) {
let next = this.next;
if(this.otherSideLoader) {
if(this.otherSideLoader && withOtherSide) {
next = next.concat(this.otherSideLoader.next);
}

4
src/index.hbs

@ -160,9 +160,7 @@ @@ -160,9 +160,7 @@
</div>
<div class="tabs-tab stickers-padding">
<div class="menu-wrapper">
<nav class="menu-horizontal-div no-stripe justify-start">
<button class="menu-horizontal-div-item btn-icon tgico-recent active"></button>
</nav>
<nav class="menu-horizontal-div no-stripe justify-start"></nav>
</div>
<div class="emoticons-content" id="content-stickers"></div>
</div>

2
src/lang.ts

@ -746,6 +746,8 @@ const lang = { @@ -746,6 +746,8 @@ const lang = {
'Clear': 'Clear',
'Save': 'Save',
'PaymentCheckoutName': 'Name',
'ClearRecentStickersAlertTitle': 'Clear recent stickers',
'ClearRecentStickersAlertMessage': 'Do you want to clear all your recent stickers?',
// * macos
'AccountSettings.Filters': 'Chat Folders',

1
src/lib/appManagers/appDialogsManager.ts

@ -1571,6 +1571,7 @@ export class AppDialogsManager { @@ -1571,6 +1571,7 @@ export class AppDialogsManager {
}, {capture: true});
// cancel link click
// ! do not change it to attachClickEvent
list.addEventListener('click', (e) => {
if(e.button === 0) {
cancelEvent(e);

80
src/lib/appManagers/appImManager.ts

@ -24,7 +24,7 @@ import {MOUNT_CLASS_TO} from '../../config/debug'; @@ -24,7 +24,7 @@ import {MOUNT_CLASS_TO} from '../../config/debug';
import appNavigationController from '../../components/appNavigationController';
import AppPrivateSearchTab from '../../components/sidebarRight/tabs/search';
import I18n, {i18n, join, LangPackKey} from '../langPack';
import {ChatFull, ChatInvite, ChatParticipant, ChatParticipants, Message, SendMessageAction} from '../../layer';
import {ChatFull, ChatInvite, ChatParticipant, ChatParticipants, Message, MessageAction, MessageMedia, SendMessageAction} from '../../layer';
import {hslaStringToHex} from '../../helpers/color';
import PeerTitle from '../../components/peerTitle';
import PopupPeer from '../../components/popups/peer';
@ -87,8 +87,10 @@ import groupCallsController from '../calls/groupCallsController'; @@ -87,8 +87,10 @@ import groupCallsController from '../calls/groupCallsController';
import callsController from '../calls/callsController';
import getFilesFromEvent from '../../helpers/files/getFilesFromEvent';
import apiManagerProxy from '../mtproto/mtprotoworker';
import wrapPeerTitle from '../../components/wrappers/peerTitle';
import appRuntimeManager from './appRuntimeManager';
import paymentsWrapCurrencyAmount from '../../helpers/paymentsWrapCurrencyAmount';
import findUpClassName from '../../helpers/dom/findUpClassName';
import {CLICK_EVENT_NAME} from '../../helpers/dom/clickEvent';
import PopupPayment from '../../components/popups/payment';
export const CHAT_ANIMATION_GROUP = 'chat';
@ -349,6 +351,59 @@ export class AppImManager extends EventListenerBase<{ @@ -349,6 +351,59 @@ export class AppImManager extends EventListenerBase<{
});
});
rootScope.addEventListener('payment_sent', async({peerId, mid, receiptMessage}) => {
const message = await this.managers.appMessagesManager.getMessageByPeer(peerId, mid);
if(!message) {
return;
}
const action = receiptMessage.action as MessageAction.messageActionPaymentSent;
toastNew({
langPackKey: 'PaymentInfoHint',
langPackArguments: [
paymentsWrapCurrencyAmount(action.total_amount, action.currency),
wrapEmojiText(((message as Message.message).media as MessageMedia.messageMediaInvoice).title)
]
});
});
(window as any).onSpoilerClick = (e: MouseEvent) => {
const spoiler = findUpClassName(e.target, 'spoiler');
const parentElement = findUpClassName(spoiler, 'message') || spoiler.parentElement;
const className = 'is-spoiler-visible';
const isVisible = parentElement.classList.contains(className);
if(!isVisible) {
cancelEvent(e);
if(CLICK_EVENT_NAME !== 'click') {
window.addEventListener('click', cancelEvent, {capture: true, once: true});
}
}
const duration = 400 / 2;
const showDuration = 5000;
const useRafs = !isVisible ? 2 : 0;
if(useRafs) {
parentElement.classList.add('will-change');
}
const spoilerTimeout = parentElement.dataset.spoilerTimeout;
if(spoilerTimeout !== null) {
clearTimeout(+spoilerTimeout);
delete parentElement.dataset.spoilerTimeout;
}
SetTransition(parentElement, className, true, duration, () => {
parentElement.dataset.spoilerTimeout = '' + window.setTimeout(() => {
SetTransition(parentElement, className, false, duration, () => {
parentElement.classList.remove('will-change');
delete parentElement.dataset.spoilerTimeout;
});
}, showDuration);
}, useRafs);
};
apiManagerProxy.addEventListener('notificationBuild', (options) => {
if(this.chat.peerId === options.message.peerId && !idleController.isIdle) {
return;
@ -360,13 +415,7 @@ export class AppImManager extends EventListenerBase<{ @@ -360,13 +415,7 @@ export class AppImManager extends EventListenerBase<{
this.addEventListener('peer_changed', async(peerId) => {
document.body.classList.toggle('has-chat', !!peerId);
let str: string;
if(peerId) {
const username = await this.managers.appPeersManager.getPeerUsername(peerId);
str = username ? '@' + username : '' + peerId;
}
appNavigationController.overrideHash(str);
this.overrideHash(peerId);
apiManagerProxy.updateTabState('chatPeerIds', this.chats.map((chat) => chat.peerId).filter(Boolean));
});
@ -1513,6 +1562,16 @@ export class AppImManager extends EventListenerBase<{ @@ -1513,6 +1562,16 @@ export class AppImManager extends EventListenerBase<{
}
};
private async overrideHash(peerId?: PeerId) {
let str: string;
if(peerId) {
const username = await this.managers.appPeersManager.getPeerUsername(peerId);
str = username ? '@' + username : '' + peerId;
}
appNavigationController.overrideHash(str);
}
public selectTab(id: number, animate?: boolean) {
if(animate === false) { // * will be used for Safari iOS history swipe
disableTransition([appSidebarLeft.sidebarEl, this.columnEl, appSidebarRight.sidebarEl]);
@ -1521,6 +1580,9 @@ export class AppImManager extends EventListenerBase<{ @@ -1521,6 +1580,9 @@ export class AppImManager extends EventListenerBase<{
document.body.classList.toggle(LEFT_COLUMN_ACTIVE_CLASSNAME, id === 0);
const prevTabId = this.tabId;
if(prevTabId !== -1) {
this.overrideHash(id > 0 ? this.chat?.peerId : undefined);
}
this.log('selectTab', id, prevTabId);

7
src/lib/appManagers/appMessagesManager.ts

@ -4030,7 +4030,8 @@ export class AppMessagesManager extends AppManager { @@ -4030,7 +4030,8 @@ export class AppMessagesManager extends AppManager {
if(message._ === 'messageService' && message.action._ === 'messageActionPaymentSent' && message.reply_to) {
this.rootScope.dispatchEvent('payment_sent', {
peerId: message.reply_to.reply_to_peer_id ? this.appPeersManager.getPeerId(message.reply_to.reply_to_peer_id) : message.peerId,
mid: message.reply_to_mid
mid: message.reply_to_mid,
receiptMessage: message
});
}
@ -4259,8 +4260,8 @@ export class AppMessagesManager extends AppManager { @@ -4259,8 +4260,8 @@ export class AppMessagesManager extends AppManager {
}
releaseUnreadCount();
this.rootScope.dispatchEvent('dialogs_multiupdate', {[peerId]: dialog});
this.dialogsStorage.setDialogToState(dialog);
this.rootScope.dispatchEvent('dialogs_multiupdate', {[peerId]: dialog});
}
};
@ -4320,8 +4321,8 @@ export class AppMessagesManager extends AppManager { @@ -4320,8 +4321,8 @@ export class AppMessagesManager extends AppManager {
if(isTopMessage || (message as Message.message).grouped_id) {
const updatedDialogs: {[peerId: PeerId]: Dialog} = {};
updatedDialogs[peerId] = dialog;
this.rootScope.dispatchEvent('dialogs_multiupdate', updatedDialogs);
this.dialogsStorage.setDialogToState(dialog);
this.rootScope.dispatchEvent('dialogs_multiupdate', updatedDialogs);
}
}
};

22
src/lib/appManagers/appStickersManager.ts

@ -72,6 +72,12 @@ export class AppStickersManager extends AppManager { @@ -72,6 +72,12 @@ export class AppStickersManager extends AppManager {
const stickerSet = update.stickerset as MyMessagesStickerSet;
this.saveStickerSet(stickerSet, stickerSet.set.id);
this.rootScope.dispatchEvent('stickers_installed', stickerSet.set);
},
updateRecentStickers: () => {
this.getRecentStickers().then(({stickers}) => {
this.rootScope.dispatchEvent('stickers_recent', stickers as MyDocument[]);
});
}
});
}
@ -103,7 +109,7 @@ export class AppStickersManager extends AppManager { @@ -103,7 +109,7 @@ export class AppStickersManager extends AppManager {
});
}
public saveStickers(docs: Document[]) {
private saveStickers(docs: Document[]) {
forEachReverse(docs, (doc, idx) => {
doc = this.appDocsManager.saveDoc(doc);
@ -295,9 +301,7 @@ export class AppStickersManager extends AppManager { @@ -295,9 +301,7 @@ export class AppStickersManager extends AppManager {
});
}
public saveStickerSet(res: Omit<MessagesStickerSet.messagesStickerSet, '_'>, id: DocId) {
// console.log('stickers save set', res);w
private saveStickerSet(res: Omit<MessagesStickerSet.messagesStickerSet, '_'>, id: DocId) {
const newSet: MessagesStickerSet = {
_: 'messages.stickerSet',
set: res.set,
@ -401,6 +405,8 @@ export class AppStickersManager extends AppManager { @@ -401,6 +405,8 @@ export class AppStickersManager extends AppManager {
}
public async toggleStickerSet(set: StickerSet.stickerSet) {
set = this.storage.getFromCache(set.id).set;
if(set.installed_date) {
const res = await this.apiManager.invokeApi('messages.uninstallStickerSet', {
stickerset: this.getStickerSetInput(set)
@ -559,7 +565,8 @@ export class AppStickersManager extends AppManager { @@ -559,7 +565,8 @@ export class AppStickersManager extends AppManager {
});
}
public pushRecentSticker(doc: MyDocument) {
public pushRecentSticker(docId: DocId) {
const doc = this.appDocsManager.getDoc(docId);
const docEmoticon = fixEmoji(doc.stickerEmojiRaw);
for(const emoticon in this.getStickersByEmoticonsPromises) {
const promise = this.getStickersByEmoticonsPromises[emoticon];
@ -573,4 +580,9 @@ export class AppStickersManager extends AppManager { @@ -573,4 +580,9 @@ export class AppStickersManager extends AppManager {
});
}
}
public clearRecentStickers() {
this.rootScope.dispatchEvent('stickers_recent', []);
return this.apiManager.invokeApi('messages.clearRecentStickers');
}
}

3
src/lib/calls/callsController.ts

@ -115,7 +115,8 @@ export class CallsController extends EventListenerBase<{ @@ -115,7 +115,8 @@ export class CallsController extends EventListenerBase<{
const {key, key_fingerprint} = await this.managers.appCallsManager.computeKey(g_a, dh.b, dh.p);
if(call.key_fingerprint !== key_fingerprint) {
this.log.error('Incorrect key fingerprint', call.key_fingerprint, key_fingerprint);
this.log.error('Incorrect key fingerprint', call.key_fingerprint, key_fingerprint, g_a, dh);
instance.hangUp('phoneCallDiscardReasonDisconnect');
break;
}

6
src/lib/crypto/computeDhKey.ts

@ -4,14 +4,14 @@ @@ -4,14 +4,14 @@
* https://github.com/morethanwords/tweb/blob/master/LICENSE
*/
import {bigIntFromBytes} from '../../helpers/bigInt/bigIntConversion';
import {bigIntFromBytes, bigIntToSigned} from '../../helpers/bigInt/bigIntConversion';
import cryptoWorker from './cryptoMessagePort';
export default async function computeDhKey(g_b: Uint8Array, a: Uint8Array, p: Uint8Array) {
const key = await cryptoWorker.invokeCrypto('mod-pow', g_b, a, p);
const keySha1Hashed = await cryptoWorker.invokeCrypto('sha1', key);
const key_fingerprint = keySha1Hashed.slice(-8).reverse(); // key_fingerprint: key_fingerprint as any // ! it doesn't work
const key_fingerprint_long = bigIntFromBytes(key_fingerprint).toString(10); // bigInt2str(str2bigInt(bytesToHex(key_fingerprint), 16), 10);
const key_fingerprint = keySha1Hashed.slice(-8).reverse();
const key_fingerprint_long = bigIntToSigned(bigIntFromBytes(key_fingerprint)).toString(10);
return {key, key_fingerprint: key_fingerprint_long};
}

48
src/lib/langPack.ts

@ -81,9 +81,16 @@ namespace I18n { @@ -81,9 +81,16 @@ namespace I18n {
let cacheLangPackPromise: Promise<LangPackDifference>;
export let lastRequestedLangCode: string;
export let lastRequestedNormalizedLangCode: string;
export let lastAppliedLangCode: string;
export let requestedServerLanguage = false;
export let timeFormat: State['settings']['timeFormat'];
function setLangCode(langCode: string) {
lastRequestedLangCode = langCode;
lastRequestedNormalizedLangCode = langCode.split('-')[0];
}
export function getCacheLangPack(): Promise<LangPackDifference> {
if(cacheLangPackPromise) return cacheLangPackPromise;
return cacheLangPackPromise = Promise.all([
@ -99,7 +106,7 @@ namespace I18n { @@ -99,7 +106,7 @@ namespace I18n {
} */
if(!lastRequestedLangCode) {
lastRequestedLangCode = langPack.lang_code;
setLangCode(langPack.lang_code);
}
applyLangPack(langPack);
@ -150,7 +157,7 @@ namespace I18n { @@ -150,7 +157,7 @@ namespace I18n {
export function loadLocalLangPack() {
const defaultCode = App.langPackCode;
lastRequestedLangCode = defaultCode;
setLangCode(defaultCode);
return Promise.all([
import('../lang'),
import('../langSign'),
@ -173,15 +180,15 @@ namespace I18n { @@ -173,15 +180,15 @@ namespace I18n {
});
}
export function loadLangPack(langCode: string) {
export function loadLangPack(langCode: string, web?: boolean) {
requestedServerLanguage = true;
const managers = rootScope.managers;
return Promise.all([
managers.apiManager.invokeApiCacheable('langpack.getLangPack', {
lang_code: langCode,
lang_pack: App.langPack
lang_pack: web ? 'web' : App.langPack
}),
managers.apiManager.invokeApiCacheable('langpack.getLangPack', {
!web && managers.apiManager.invokeApiCacheable('langpack.getLangPack', {
lang_code: langCode,
lang_pack: 'android'
}),
@ -225,20 +232,16 @@ namespace I18n { @@ -225,20 +232,16 @@ namespace I18n {
return pushTo;
}
export function getLangPack(langCode: string) {
lastRequestedLangCode = langCode;
return loadLangPack(langCode).then(([langPack1, langPack2, localLangPack1, localLangPack2, countries, _]) => {
export function getLangPack(langCode: string, web?: boolean) {
setLangCode(langCode);
return loadLangPack(langCode, web).then(([langPack1, langPack2, localLangPack1, localLangPack2, countries, _]) => {
let strings: LangPackString[] = [];
[localLangPack1, localLangPack2].forEach((l) => {
formatLocalStrings(l.default as any, strings);
});
strings = strings.concat(langPack1.strings);
for(const string of langPack2.strings) {
strings.push(string);
}
strings = strings.concat(...[langPack1.strings, langPack2.strings].filter(Boolean));
langPack1.strings = strings;
langPack1.countries = countries;
@ -266,10 +269,18 @@ namespace I18n { @@ -266,10 +269,18 @@ namespace I18n {
})();
export function applyLangPack(langPack: LangPackDifference) {
if(langPack.lang_code !== lastRequestedLangCode) {
const currentLangCode = lastRequestedLangCode;
if(langPack.lang_code !== currentLangCode) {
return;
}
try {
pluralRules = new Intl.PluralRules(lastRequestedNormalizedLangCode);
} catch(err) {
console.error('pluralRules error', err);
pluralRules = new Intl.PluralRules(lastRequestedNormalizedLangCode.split('-', 1)[0]);
}
try {
pluralRules = new Intl.PluralRules(langPack.lang_code);
} catch(err) {
@ -299,9 +310,9 @@ namespace I18n { @@ -299,9 +310,9 @@ namespace I18n {
});
}
if(lastAppliedLangCode !== langPack.lang_code) {
rootScope.dispatchEvent('language_change', langPack.lang_code);
lastAppliedLangCode = langPack.lang_code;
if(lastAppliedLangCode !== currentLangCode) {
rootScope.dispatchEvent('language_change', currentLangCode);
lastAppliedLangCode = currentLangCode;
cachedDateTimeFormats.clear();
updateAmPm();
}
@ -508,7 +519,8 @@ namespace I18n { @@ -508,7 +519,8 @@ namespace I18n {
const json = JSON.stringify(options);
let dateTimeFormat = cachedDateTimeFormats.get(json);
if(!dateTimeFormat) {
cachedDateTimeFormats.set(json, dateTimeFormat = new Intl.DateTimeFormat(lastRequestedLangCode + '-u-hc-' + timeFormat, options));
dateTimeFormat = new Intl.DateTimeFormat(lastRequestedNormalizedLangCode + '-u-hc-' + timeFormat, options);
cachedDateTimeFormats.set(json, dateTimeFormat);
}
return dateTimeFormat;

19
src/lib/mediaPlayer.ts

@ -11,7 +11,6 @@ import cancelEvent from '../helpers/dom/cancelEvent'; @@ -11,7 +11,6 @@ import cancelEvent from '../helpers/dom/cancelEvent';
import ListenerSetter, {Listener} from '../helpers/listenerSetter';
import ButtonMenu from '../components/buttonMenu';
import {ButtonMenuToggleHandler} from '../components/buttonMenuToggle';
import rootScope from './rootScope';
import ControlsHover from '../helpers/dom/controlsHover';
import {addFullScreenListener, cancelFullScreen, isFullScreen, requestFullScreen} from '../helpers/dom/fullScreen';
import toHHMMSS from '../helpers/string/toHHMMSS';
@ -20,6 +19,7 @@ import VolumeSelector from '../components/volumeSelector'; @@ -20,6 +19,7 @@ import VolumeSelector from '../components/volumeSelector';
import debounce from '../helpers/schedulers/debounce';
import overlayCounter from '../helpers/overlayCounter';
import onMediaLoad from '../helpers/onMediaLoad';
import {attachClickEvent} from '../helpers/dom/clickEvent';
export default class VideoPlayer extends ControlsHover {
private static PLAYBACK_RATES = [0.5, 1, 1.5, 2];
@ -97,7 +97,6 @@ export default class VideoPlayer extends ControlsHover { @@ -97,7 +97,6 @@ export default class VideoPlayer extends ControlsHover {
}).finally(() => { // due to autoplay, play will not call
this.wrapper.classList.toggle('is-playing', !this.video.paused);
});
// (this.wrapper.querySelector('.toggle') as HTMLButtonElement).click();
}
}
@ -127,15 +126,15 @@ export default class VideoPlayer extends ControlsHover { @@ -127,15 +126,15 @@ export default class VideoPlayer extends ControlsHover {
leftControls.insertBefore(volumeSelector.btn, timeElapsed.parentElement);
Array.from(toggle).forEach((button) => {
listenerSetter.add(button)('click', () => {
attachClickEvent(button, () => {
this.togglePlay();
});
}, {listenerSetter: this.listenerSetter});
});
if(this.pipButton) {
listenerSetter.add(this.pipButton)('click', () => {
attachClickEvent(this.pipButton, () => {
this.video.requestPictureInPicture();
});
}, {listenerSetter: this.listenerSetter});
const onPip = (pip: boolean) => {
this.wrapper.style.visibility = pip ? 'hidden': '';
@ -170,9 +169,9 @@ export default class VideoPlayer extends ControlsHover { @@ -170,9 +169,9 @@ export default class VideoPlayer extends ControlsHover {
}
if(!IS_TOUCH_SUPPORTED) {
listenerSetter.add(video)('click', () => {
attachClickEvent(video, () => {
this.togglePlay();
});
}, {listenerSetter: this.listenerSetter});
listenerSetter.add(document)('keydown', (e: KeyboardEvent) => {
if(overlayCounter.overlaysActive > 1 || document.pictureInPictureElement === video) { // forward popup is active, etc
@ -216,9 +215,9 @@ export default class VideoPlayer extends ControlsHover { @@ -216,9 +215,9 @@ export default class VideoPlayer extends ControlsHover {
}
});
listenerSetter.add(fullScreenButton)('click', () => {
attachClickEvent(fullScreenButton, () => {
this.toggleFullScreen();
});
}, {listenerSetter: this.listenerSetter});
addFullScreenListener(wrapper, this.onFullScreen.bind(this, fullScreenButton), listenerSetter);

16
src/lib/mtproto/tl_utils.ts

@ -17,16 +17,14 @@ import isObject from '../../helpers/object/isObject'; @@ -17,16 +17,14 @@ import isObject from '../../helpers/object/isObject';
import gzipUncompress from '../../helpers/gzipUncompress';
import bigInt from 'big-integer';
import ulongFromInts from '../../helpers/long/ulongFromInts';
import { safeBigInt } from '../../helpers/bigInt/bigIntConstants';
import { bigIntToSigned, bigIntToUnsigned } from '../../helpers/bigInt/bigIntConversion';
const boolFalse = +Schema.API.constructors.find((c) => c.predicate === 'boolFalse').id;
const boolTrue = +Schema.API.constructors.find((c) => c.predicate === 'boolTrue').id;
const vector = +Schema.API.constructors.find((c) => c.predicate === 'vector').id;
const gzipPacked = +Schema.MTProto.constructors.find((c) => c.predicate === 'gzip_packed').id;
const safeBigInt = bigInt(Number.MAX_SAFE_INTEGER);
const ulongBigInt = bigInt(bigInt[2]).pow(64);
const longBigInt = ulongBigInt.divide(bigInt[2]);
class TLSerialization {
private maxLength = 2048; // 2Kb
private offset = 0; // in bytes
@ -151,11 +149,7 @@ class TLSerialization { @@ -151,11 +149,7 @@ class TLSerialization {
}
}
let _bigInt = bigInt(sLong as string);
if(_bigInt.isNegative()) { // make it unsigned
_bigInt = ulongBigInt.add(_bigInt);
}
const _bigInt = bigIntToUnsigned(bigInt(sLong as string));
const {quotient, remainder} = _bigInt.divmod(0x100000000);
const high = quotient.toJSNumber();
const low = remainder.toJSNumber();
@ -504,8 +498,8 @@ class TLDeserialization<FetchLongAs extends Long> { @@ -504,8 +498,8 @@ class TLDeserialization<FetchLongAs extends Long> {
const iHigh = this.readInt((field || '') + ':long[high]');
let ulong = ulongFromInts(iHigh, iLow);
if(/* !unsigned && */!this.mtproto && ulong.greater(longBigInt)) { // make it signed
ulong = ulong.minus(ulongBigInt);
if(/* !unsigned && */!this.mtproto) { // make it signed
ulong = bigIntToSigned(ulong);
}
if(!this.mtproto) {

5
src/lib/richTextProcessor/wrapRichText.ts

@ -18,6 +18,7 @@ import parseEntities from './parseEntities'; @@ -18,6 +18,7 @@ import parseEntities from './parseEntities';
import setBlankToAnchor from './setBlankToAnchor';
import wrapUrl from './wrapUrl';
import EMOJI_VERSIONS_SUPPORTED from '../../environment/emojiVersionsSupport';
import {CLICK_EVENT_NAME} from '../../helpers/dom/clickEvent';
/**
* * Expecting correctly sorted nested entities (RichTextProcessor.sortEntities)
@ -400,6 +401,8 @@ export default function wrapRichText(text: string, options: Partial<{ @@ -400,6 +401,8 @@ export default function wrapRichText(text: string, options: Partial<{
usedText = true;
container.append(element);
fragment.append(container);
container[`on${CLICK_EVENT_NAME}`] = (window as any).onSpoilerClick;
}
break;
@ -419,7 +422,7 @@ export default function wrapRichText(text: string, options: Partial<{ @@ -419,7 +422,7 @@ export default function wrapRichText(text: string, options: Partial<{
(lastElement || fragment).append(element);
}
while(nextEntity && nextEntity.offset < (endOffset - 1)) {
while(nextEntity && nextEntity.offset < endOffset) {
++nasty.i;
(element || fragment).append(wrapRichText(nasty.text, {

4
src/lib/rlottie/queryableWorker.ts

@ -47,7 +47,7 @@ export default class QueryableWorker extends EventListenerBase<{ @@ -47,7 +47,7 @@ export default class QueryableWorker extends EventListenerBase<{
queryMethodArguments: args
});
} else {
const transfer: (ArrayBuffer | OffscreenCanvas)[] = [];
const transfer: Transferable[] = [];
args.forEach((arg) => {
if(arg instanceof ArrayBuffer) {
transfer.push(arg);
@ -62,7 +62,7 @@ export default class QueryableWorker extends EventListenerBase<{ @@ -62,7 +62,7 @@ export default class QueryableWorker extends EventListenerBase<{
this.worker.postMessage({
queryMethod: queryMethod,
queryMethodArguments: args
}, transfer as Transferable[]);
}, transfer);
}
}
}

4
src/lib/rootScope.ts

@ -16,6 +16,7 @@ import type {AppManagers} from './appManagers/managers'; @@ -16,6 +16,7 @@ import type {AppManagers} from './appManagers/managers';
import type {State} from '../config/state';
import type {Progress} from './appManagers/appDownloadManager';
import type {CallId} from './appManagers/appCallsManager';
import type {MyDocument} from './appManagers/appDocsManager';
import {NULL_PEER_ID, UserAuth} from './mtproto/mtproto_config';
import EventListenerBase from '../helpers/eventListenerBase';
import {MOUNT_CLASS_TO} from '../config/debug';
@ -86,6 +87,7 @@ export type BroadcastEvents = { @@ -86,6 +87,7 @@ export type BroadcastEvents = {
'stickers_installed': StickerSet.stickerSet,
'stickers_deleted': StickerSet.stickerSet,
'stickers_recent': MyDocument[],
'state_cleared': void,
'state_synchronized': ChatId | void,
@ -139,7 +141,7 @@ export type BroadcastEvents = { @@ -139,7 +141,7 @@ export type BroadcastEvents = {
'logging_out': void,
'payment_sent': {peerId: PeerId, mid: number},
'payment_sent': {peerId: PeerId, mid: number, receiptMessage: Message.messageService}
'premium_toggle': boolean,

83
src/scripts/out/langPack.strings

@ -48,6 +48,8 @@ @@ -48,6 +48,8 @@
"Contacts.Count_other" = "%d contacts";
"Deactivated.Title" = "Too many tabs...";
"Deactivated.Subtitle" = "Telegram supports only one active tab with the app.\nClick anywhere to continue using this tab.";
"Deactivated.Version.Title" = "WebK has updated...";
"Deactivated.Version.Subtitle" = "Another tab is running a newer version of Telegram.\nClick anywhere to reload this tab.";
"General.Keyboard" = "Keyboard";
"General.SendShortcut.Enter" = "Send by Enter";
"General.SendShortcut.CtrlEnter" = "Send by %s + Enter";
@ -90,7 +92,6 @@ @@ -90,7 +92,6 @@
"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";
"TwoStepAuth.InvalidPassword" = "Invalid password";
"TwoStepAuth.EmailCodeChangeEmail" = "Change Email";
"MarkupTooltip.LinkPlaceholder" = "Enter URL...";
"MediaViewer.Context.Download" = "Download";
@ -109,6 +110,13 @@ @@ -109,6 +110,13 @@
"PushNotification.Action.Settings.Mobile" = "Alerts settings";
"PushNotification.Message.NoPreview" = "You have a new message";
"LogOut.Description" = "Are you sure you want to log out?\n\nNote that you can seamlessly use Telegram on all your devices at once.";
"VoiceChat.DiscussionGroup" = "discussion group";
"PaymentInfo.CVV" = "CVV Code";
"PaymentInfo.Card.Title" = "Enter your card information";
"PaymentInfo.Billing.Title" = "Enter your billing address";
"PaymentInfo.Done" = "PROCEED TO CHECKOUT";
"PaymentCard.Error.Invalid" = "Invalid card number";
"PaymentCard.Error.Incomplete" = "Incomplete card number";
"AccDescrEditing" = "Editing";
"ActionCreateChannel" = "Channel created";
"ActionCreateGroup" = "un1 created the group";
@ -599,6 +607,67 @@ @@ -599,6 +607,67 @@
"ResetAutomaticMediaDownloadAlertTitle" = "Reset settings";
"ResetAutomaticMediaDownloadAlert" = "Are you sure you want to reset auto-download settings?";
"Reset" = "Reset";
"SendMessageAsTitle" = "Send message as...";
"Devices" = "Devices";
"LanguageName" = "English";
"EditCantEditPermissionsPublic" = "This permission is not available in public groups.";
"VoipUserMicrophoneIsOff" = "%s's microphone is off";
"VoipUserCameraIsOff" = "%s's camera is off";
"PrivacyPhoneInfo4" = "This public link opens a chat with you:";
"ReportChatIllegalDrugs" = "Illegal Drugs";
"ReportChatPersonalDetails" = "Personal Details";
"VoipPeerIncompatible" = "**%1$s**'s app is using an incompatible protocol. They need to update their app before you can call them.";
"ScamMessage" = "SCAM";
"FakeMessage" = "FAKE";
"TextCopied" = "Text copied to clipboard";
"PaymentInvoice" = "INVOICE";
"PaymentTestInvoice" = "TEST INVOICE";
"PaymentReceipt" = "Receipt";
"PaymentSuccessfullyPaid" = "You successfully transferred %1$s to %2$s for %3$s";
"PaymentSuccessfullyPaidNoItem" = "You successfully transferred %1$s to %2$s";
"PaymentCheckout" = "Checkout";
"PaymentTransactionTotal" = "Total";
"PaymentTip" = "Tip";
"PaymentTipOptional" = "Tip (Optional)";
"PaymentCheckoutPay" = "PAY %1$s";
"PaymentCheckoutMethod" = "Payment method";
"PaymentCheckoutProvider" = "Payment provider";
"PaymentCardNumber" = "Card Number";
"PaymentCardSavePaymentInformation" = "Save Payment Information";
"PaymentCardInfo" = "Payment info";
"PaymentCardSavePaymentInformationInfoLine1" = "You can save your payment info for future use. It will be stored directly with the payment provider. Telegram has no access to your credit card data.";
"Done" = "Done";
"PaymentShippingMethod" = "Shipping methods";
"PaymentNoShippingMethod" = "Sorry, it is not possible to deliver to your address.";
"PaymentShippingInfo" = "Shipping Information";
"PaymentShippingAddress" = "Shipping address";
"PaymentShippingAddress1Placeholder" = "Address 1 (Street)";
"PaymentShippingAddress2Placeholder" = "Address 2 (Street)";
"PaymentShippingCityPlaceholder" = "City";
"PaymentShippingStatePlaceholder" = "State";
"PaymentShippingCountry" = "Country";
"PaymentShippingZipPlaceholder" = "Postcode";
"PaymentShippingReceiver" = "Receiver";
"PaymentShippingName" = "Full Name";
"PaymentShippingEmailPlaceholder" = "Email";
"PaymentCheckoutPhoneNumber" = "Phone number";
"PaymentCheckoutShippingMethod" = "Shipping method";
"PaymentShippingSave" = "Save Shipping Information";
"PaymentShippingSaveInfo" = "You can save your shipping info for future use.";
"PaymentInfoHint" = "You paid **%1$s** for **%2$s**.";
"PrivacyPayments" = "Payments";
"PrivacyPaymentsClearInfo" = "You can delete your shipping info and instruct all payment providers to remove your saved credit cards. Note that Telegram never stores your credit card data.";
"PrivacyPaymentsClear" = "Clear Payment and Shipping Info";
"PrivacyPaymentsClearAlertTitle" = "Clear payment info";
"PrivacyPaymentsClearAlertText" = "Are you sure you want to clear your payment and shipping info?";
"PrivacyPaymentsPaymentInfoCleared" = "Payment info cleared.";
"PrivacyPaymentsShippingInfoCleared" = "Shipping info cleared.";
"PrivacyPaymentsPaymentShippingCleared" = "Payment and shipping info cleared.";
"PrivacyClearShipping" = "Shipping info";
"PrivacyClearPayment" = "Payment info";
"Clear" = "Clear";
"Save" = "Save";
"PaymentCheckoutName" = "Name";
"AccountSettings.Filters" = "Chat Folders";
"AccountSettings.Notifications" = "Notifications and Sounds";
"AccountSettings.PrivacyAndSecurity" = "Privacy and Security";
@ -705,6 +774,7 @@ @@ -705,6 +774,7 @@
"Chat.Send.WithoutSound" = "Send Without Sound";
"Chat.Send.SetReminder" = "Set a Reminder";
"Chat.Send.ScheduledMessage" = "Schedule Message";
"Chat.SendAs.PersonalAccount" = "personal account";
"Chat.UnpinAllMessagesConfirmation_one" = "Do you want to unpin %d message in this chat?";
"Chat.UnpinAllMessagesConfirmation_other" = "Do you want to unpin all %d messages in this chat?";
"Chat.Message.Ad.Text" = "Unlike other apps, Telegram never uses your private data to target ads. Sponsored messages on Telegram are based solely on the topic of the public channels in which they are shown. This means that no user data is mined or analyzed to display ads, and every user viewing a channel on Telegram sees the same sponsored messages.\n\nUnlike other apps, Telegram doesn't track whether you tapped on a sponsored message and doesn't profile you based on your activity. We also prevent external links in sponsored messages to ensure that third parties can’t spy on our users. We believe that everyone has the right to privacy, and technological platforms should respect that.\n\nTelegram offers a free and unlimited service to hundreds of millions of users, which involves significant server and traffic costs. In order to remain independent and stay true to its values, Telegram developed a paid tool to promote messages with user privacy in mind. We welcome responsible advertisers at:\n\n%@\n\nSponsored Messages are currently in test mode. Once they are fully launched and allow Telegram to cover its basic costs, we will start sharing ad revenue with the owners of public channels in which sponsored messages are displayed.\n\nOnline ads should no longer be synonymous with abuse of user privacy. Let us redefine how a tech company should operate – together.";
@ -713,6 +783,12 @@ @@ -713,6 +783,12 @@
"Chat.Message.ViewBot" = "VIEW BOT";
"Chat.Message.ViewGroup" = "VIEW GROUP";
"Chat.Message.Sponsored.What" = "What are sponsored messages?";
"Checkout.2FA.Text" = "Saving payment details is only available with 2-Step Verification.";
"Checkout.NewCard.CardholderNamePlaceholder" = "Cardholder Name";
"Checkout.PasswordEntry.Title" = "Payment Confirmation";
"Checkout.PasswordEntry.Pay" = "Pay";
"Checkout.PasswordEntry.Text" = "Your card %@ is on file. To pay with this card, please enter your 2-Step-Verification password.";
"Checkout.WebConfirmation.Title" = "Complete Payment";
"ChatList.Context.Mute" = "Mute";
"ChatList.Context.Unmute" = "Unmute";
"ChatList.Context.Pin" = "Pin";
@ -769,6 +845,7 @@ @@ -769,6 +845,7 @@
"Emoji.TravelAndPlaces" = "Travel & Places";
"Emoji.Objects" = "Objects";
"Emoji.Flags" = "Flags";
"Error.AnError" = "An error occurred. Please try again later.";
"FileSize.B" = "%@ B";
"FileSize.KB" = "%@ KB";
"FileSize.MB" = "%@ MB";
@ -781,6 +858,7 @@ @@ -781,6 +858,7 @@
"Message.Context.Pin" = "Pin";
"Message.Context.Unpin" = "Unpin";
"Message.Context.Goto" = "Show Message";
"Message.ReplyActionButtonShowReceipt" = "Show Receipt";
"MessageContext.CopyMessageLink1" = "Copy Message Link";
"Modal.Send" = "Send";
"NewPoll.Anonymous" = "Anonymous Voting";
@ -890,6 +968,7 @@ @@ -890,6 +968,7 @@
"GeneralSettings.EmojiPrediction" = "Suggest Emoji";
"GroupPermission.Delete" = "Delete Exception";
"Search.Confirm.ClearHistory" = "Are you sure you want to clear your search history?";
"SecureId.Identity.Placeholder.ExpiryDate" = "Expiry Date";
"Separator.ShowMore" = "show more";
"Separator.ShowLess" = "show less";
"ScheduleController.at" = "at";
@ -946,7 +1025,6 @@ @@ -946,7 +1025,6 @@
"VoiceChat.RemovePeer.Confirm" = "Are you sure you want to remove %1$@ from the group?";
"VoiceChat.RemovePeer.Confirm.OK" = "Remove";
"Login.Title" = "Sign in to Telegram";
"Login.CountrySelectorLabel" = "Country";
"Login.PhoneLabel" = "Phone Number";
"Login.PhoneLabelInvalid" = "Phone Number Invalid";
"Login.KeepSigned" = "Keep me signed in";
@ -965,6 +1043,7 @@ @@ -965,6 +1043,7 @@
"FirstName" = "First name (required)";
"LastName" = "Last name (optional)";
"StartMessaging" = "Start Messaging";
"Country" = "Country";
"Contacts.PhoneNumber.Placeholder" = "Phone Number";
"Login.Next" = "Next";
"Login.ContinueOnLanguage" = "Continue in English";

4
src/scss/components/_global.scss

@ -164,6 +164,10 @@ Utility Classes @@ -164,6 +164,10 @@ Utility Classes
white-space: pre-wrap !important;
}
.no-wrap {
white-space: nowrap !important;
}
.no-border-radius {
border-radius: 0 !important;
}

32
src/scss/fonts/_roboto.scss

@ -57,3 +57,35 @@ @@ -57,3 +57,35 @@
src: local('Roboto Medium'), local('Roboto-Medium'), url(assets/fonts/KFOlCnqEu92Fr1MmEU9fBBc4AMP6lQ.woff2) format('woff2');
unicode-range:U + 0000-00FF, U + 0131, U + 0152-0153, U + 02BB-02BC, U + 02C6, U + 02DA, U + 02DC, U + 2000-206F, U + 2074, U + 20AC, U + 2122, U + 2191, U + 2193, U + 2212, U + 2215, U + FEFF, U + FFFD
}
// * fix bold formatting
/* cyrillic */
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 600;
font-display: swap;
src: local('Roboto Medium'), local('Roboto-Medium'), url(assets/fonts/KFOlCnqEu92Fr1MmEU9fABc4AMP6lbBP.woff2) format('woff2');
unicode-range:U + 0400-045F, U + 0490-0491, U + 04B0-04B1, U + 2116
}
/* latin-ext */
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 600;
font-display: swap;
src: local('Roboto Medium'), local('Roboto-Medium'), url(assets/fonts/KFOlCnqEu92Fr1MmEU9fChc4AMP6lbBP.woff2) format('woff2');
unicode-range:U + 0100-024F, U + 0259, U + 1E00-1EFF, U + 2020, U + 20A0-20AB, U + 20AD-20CF, U + 2113, U + 2C60-2C7F, U + A720-A7FF
}
/* latin */
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 600;
font-display: swap;
src: local('Roboto Medium'), local('Roboto-Medium'), url(assets/fonts/KFOlCnqEu92Fr1MmEU9fBBc4AMP6lQ.woff2) format('woff2');
unicode-range:U + 0000-00FF, U + 0131, U + 0152-0153, U + 02BB-02BC, U + 02C6, U + 02DA, U + 02DC, U + 2000-206F, U + 2074, U + 20AC, U + 2122, U + 2191, U + 2193, U + 2212, U + 2215, U + FEFF, U + FFFD
}

3
src/scss/partials/_chatBubble.scss

@ -2799,6 +2799,7 @@ $bubble-beside-button-width: 38px; @@ -2799,6 +2799,7 @@ $bubble-beside-button-width: 38px;
overflow: hidden;
min-height: 2.5rem;
display: flex;
border-radius: .375rem;
&:last-child {
border-bottom-left-radius: $border-radius-big;
@ -2808,7 +2809,7 @@ $bubble-beside-button-width: 38px; @@ -2808,7 +2809,7 @@ $bubble-beside-button-width: 38px;
&-button {
padding: .5625rem 0;
border-radius: 6px;
border-radius: inherit;
z-index: 2;
font-size: .875rem;
user-select: none;

2
src/scss/partials/_ckin.scss

@ -388,7 +388,7 @@ video::-webkit-media-controls-enclosure { @@ -388,7 +388,7 @@ video::-webkit-media-controls-enclosure {
}
&.is-focused .progress-line__filled:not(.progress-line__loaded):after {
transform: translateX(calc(var(--thumb-size) / 2)) scale(1.25);
transform: translateX(calc(var(--thumb-size) / 2)) scale(1.125);
}
&__loaded, &:before {

230
src/scss/partials/_emojiDropdown.scss

@ -5,6 +5,7 @@ @@ -5,6 +5,7 @@
*/
.emoji-dropdown {
--menu-height: 3.0625rem;
display: flex/* !important */;
flex-direction: column;
width: 100%;
@ -41,16 +42,16 @@ @@ -41,16 +42,16 @@
}
}
/* @include respond-to(handhelds) {
width: calc(100% + 1rem);
margin-left: -.5rem;
} */
.emoji-container {
width: 100%;
max-width: 100%;
overflow: hidden;
height: 100%;
.menu-horizontal-div {
z-index: 4;
background-color: var(--surface-color);
}
}
.emoji-tabs {
@ -62,34 +63,43 @@ @@ -62,34 +63,43 @@
&-search {
position: absolute;
left: 0;
margin-left: 4px !important;
margin-left: .5rem !important;
}
&-delete {
position: absolute;
right: 0;
margin-right: 4px !important;
margin-right: .5rem !important;
}
.menu-horizontal-div-item {
margin: 0 1rem;
}
}
.category-title {
font-size: var(--font-size-14);
font-weight: var(--font-weight-bold);
line-height: var(--line-height-14);
color: var(--secondary-text-color);
padding: .8125rem .875rem .6875rem;
width: 100%;
position: relative;
.btn-icon {
position: absolute;
right: 0.5rem;
top: 50%;
transform: translateY(-50%);
font-size: 1.25rem;
z-index: 1;
pointer-events: all;
}
}
.tabs-container {
/* width: 300%; */
height: 100%;
.category-title {
//position: sticky;
top: 0;
//font-size: .85rem;
font-size: 14px;
font-weight: var(--font-weight-bold);
color: var(--secondary-text-color);
//background: linear-gradient(to bottom,#fff 0,rgba(255,255,255,.9) 60%,rgba(255,255,255,0) 100%);
z-index: 2;
//padding: .53333rem 6PX .66667rem;
padding: 12px 6px 6px 6px;
width: 100%;
}
.sticky_sentinel {
&--top {
top: 0;
@ -110,173 +120,79 @@ @@ -110,173 +120,79 @@
position: relative;
background-color: var(--surface-color);
}
.scrollable {
padding: 0 10px;
}
}
.emoji-padding.active {
.emoji-padding {
.super-emojis {
padding: 0 .5rem;
}
@include respond-to(handhelds) {
.menu-horizontal-div .menu-horizontal-div-item {
.menu-horizontal-div-item {
flex: unset;
padding: 0;
}
.category-items {
grid-template-columns: repeat(auto-fill, 40px);
> span {
width: 40px;
height: 40px;
justify-self: center;
}
}
.category-title {
padding: 12px 6px 6px 10px;
}
.scrollable {
padding: 0;
}
.emoji-category .category-items {
grid-column-gap: unset;
}
}
}
.emoji-padding,
.stickers-padding {
.menu-horizontal-div {
// padding: 2px;
z-index: 4;
background-color: var(--surface-color);
.menu-horizontal-div-item {
margin: 0;
}
}
}
.emoji-category {
//padding-top: 1px;
position: relative;
margin: 0 -.125rem;
/* &:first-child {
//padding-top: 5px;
} */
/* &::after {
content: "";
flex: auto;
} */
}
.sticker-category {
position: relative;
.category-title {
cursor: pointer;
}
&.stickers-recent {
.category-title {
pointer-events: none;
}
}
/* &::after {
content: "";
flex: auto;
} */
/* &.not-full::after {
content: "";
flex: auto;
} */
.category-items {
width: 100%;
display: grid;
grid-template-columns: repeat(auto-fill, var(--esg-sticker-size)); // 64px
grid-column-gap: 1px;
justify-content: space-between;
}
}
#content-stickers {
.scrollable {
padding: 0px 5px 0;
}
}
.menu-horizontal-div {
width: 100%;
height: 48px;
height: var(--menu-height);
min-height: var(--menu-height);
align-items: center;
.menu-horizontal-div-item {
&-item {
font-size: 1.5rem;
margin: 0 12px;
width: 48px;
height: 48px;
line-height: 48px;
width: 2.5rem;
height: 2.5rem;
line-height: 2.5rem;
display: flex;
align-items: center;
flex: 0 0 auto;
padding: 0;
}
}
.stickers-padding {
&.active {
.scrollable {
padding: 0;
//box-shadow: 0px 1px 5px -1px rgba(0, 0, 0, .21);
}
.category-title {
cursor: pointer;
}
.menu-horizontal-div {
.menu-horizontal-div-item {
height: 48px;
width: 48px;
padding: 0;
margin-right: 1px;
margin-left: 1px;
}
}
.category-items {
padding: 0 .3125rem;
}
.menu-wrapper {
padding: 0;
height: 48px;
height: var(--menu-height);
max-width: 100%;
position: relative;
border-bottom: 1px solid var(--border-color);
background-color: var(--surface-color);
}
.menu-horizontal-div-item {
/* width: calc(100% / 7); */
flex: 0 0 auto;
&.active {
&:not(.tgico-recent) {
background-color: var(--light-secondary-text-color);
}
}
> canvas, > img {
//padding: .75rem;
padding: 8px;
max-width: 100%;
max-height: 100%;
}
> canvas {
width: 100%;
height: 100%;
}
}
.menu-horizontal-div {
&-item {
flex: 0 0 auto;
padding: .25rem;
margin: 0 .3125rem;
&-padding {
width: 100%;
height: 100%;
position: relative;
}
&.active {
&:not(.tgico-recent) {
background-color: var(--light-secondary-text-color);
}
}
}
}
}
}

1
src/scss/partials/popups/_call.scss

@ -10,6 +10,7 @@ @@ -10,6 +10,7 @@
#{$parent} {
&-header {
width: 100%;
justify-content: space-between;
}
&-title {

1
src/scss/partials/popups/_forward.scss

@ -29,6 +29,7 @@ @@ -29,6 +29,7 @@
&-title {
flex-grow: 1;
padding: 0;
}
}

1
src/scss/partials/popups/_groupCall.scss

@ -74,6 +74,7 @@ @@ -74,6 +74,7 @@
&-title,
&-subtitle {
font-size: var(--font-size-16);
line-height: var(--line-height);
@include text-overflow();

3
src/scss/partials/popups/_payment.scss

@ -84,7 +84,8 @@ @@ -84,7 +84,8 @@
.payment-verification {
width: 100%;
min-height: 30rem;
height: 40rem;
max-height: 100%;
border: none;
flex: 1 1 auto;
}

2
src/scss/partials/popups/_popup.scss

@ -55,7 +55,7 @@ @@ -55,7 +55,7 @@
&-title {
flex: 1;
padding: 0 2rem 0 1.5rem;
padding: 0 1rem 0 1.5rem;
margin: 0;
font-size: 1.25rem;
font-weight: var(--font-weight-bold);

20
src/scss/style.scss

@ -542,9 +542,9 @@ input, [contenteditable=true] { @@ -542,9 +542,9 @@ input, [contenteditable=true] {
background-color: transparent;
}
[contenteditable] [style*="bold"] {
font-weight: var(--font-weight-bold) !important;
}
// [contenteditable] [style*="bold"] {
// font-weight: var(--font-weight-bold) !important;
// }
input, textarea {
-webkit-appearance: none;
@ -969,13 +969,13 @@ img.emoji { @@ -969,13 +969,13 @@ img.emoji {
width: 100%;
display: grid;
grid-template-columns: repeat(auto-fill, var(--esg-sticker-size)); // 64px
grid-column-gap: 1px;
// grid-column-gap: 1px;
justify-content: space-between;
}
.super-sticker {
@include hover-background-effect() {
border-radius: 10px;
border-radius: $border-radius-medium;
}
/* &:nth-child(5n+5) {
@ -1269,6 +1269,16 @@ middle-ellipsis-element { @@ -1269,6 +1269,16 @@ middle-ellipsis-element {
// }
// }
.media-container-contain {
position: relative;
.media-photo {
object-fit: contain;
max-width: 100%;
max-height: 100%;
}
}
.media-container-cover {
position: relative;

80
webpack.common.js

@ -37,7 +37,7 @@ const opts = { @@ -37,7 +37,7 @@ const opts = {
'version': 3,
'ifdef-verbose': devMode, // add this for verbose output
'ifdef-triple-slash': false, // add this to use double slash comment instead of default triple slash
'ifdef-fill-with-blanks': true, // add this to remove code with blank spaces instead of "//" comments
'ifdef-fill-with-blanks': true // add this to remove code with blank spaces instead of "//" comments
};
const domain = 'yourdomain.com';
@ -45,17 +45,17 @@ const localIp = '192.168.92.78'; @@ -45,17 +45,17 @@ const localIp = '192.168.92.78';
const middleware = (req, res, next) => {
let IP = '';
if (req.headers['cf-connecting-ip']) {
if(req.headers['cf-connecting-ip']) {
IP = req.headers['cf-connecting-ip'];
} else {
IP = req.connection.remoteAddress.split(':').pop();
}
if (!allowedIPs.includes(IP) && !/^192\.168\.\d{1,3}\.\d{1,3}$/.test(IP)) {
if(!allowedIPs.includes(IP) && !/^192\.168\.\d{1,3}\.\d{1,3}$/.test(IP)) {
console.log('Bad IP connecting: ' + IP, req.url);
res.status(404).send('Nothing interesting here.');
} else {
if (req.url.indexOf('/assets/') !== 0) {
if(req.url.indexOf('/assets/') !== 0) {
console.log(req.url, IP);
}
@ -83,8 +83,8 @@ module.exports = { @@ -83,8 +83,8 @@ module.exports = {
{
loader: 'css-loader',
options: {
url: false,
},
url: false
}
},
devMode ? undefined : MediaQueryPlugin.loader,
@ -100,18 +100,18 @@ module.exports = { @@ -100,18 +100,18 @@ module.exports = {
// Webpack 5
postcssOptions: {
plugins: [
postcssPresetEnv(),
],
},
},
postcssPresetEnv()
]
}
}
},
{
loader: 'sass-loader',
options: {
sourceMap: devMode,
},
},
].filter(Boolean),
sourceMap: devMode
}
}
].filter(Boolean)
},
// {
// test: /\.worker\.ts$/i,
@ -125,26 +125,26 @@ module.exports = { @@ -125,26 +125,26 @@ module.exports = {
use: [
// { loader: 'babel-loader', options: require('./babel.config') },
'ts-loader',
{loader: 'ifdef-loader', options: opts},
{loader: 'ifdef-loader', options: opts}
],
exclude: /node_modules/,
exclude: /node_modules/
},
{
test: /\.hbs$/,
loader: 'handlebars-loader',
options: {
helperDirs: __dirname + '/handlebarsHelpers',
},
helperDirs: __dirname + '/handlebarsHelpers'
}
// loader: 'handlebars-loader?helperDirs[]=' + __dirname + '/handlebarsHelpers',
// use: [
// 'handlebars-loader'
// ]
},
],
}
]
},
resolve: {
extensions: ['.ts', '.js'],
extensions: ['.ts', '.js']
},
entry: './src/index.ts',
@ -166,8 +166,8 @@ module.exports = { @@ -166,8 +166,8 @@ module.exports = {
// Webpack 5
clean: {
keep: keepAsset,
},
keep: keepAsset
}
},
devServer: {
@ -188,10 +188,10 @@ module.exports = { @@ -188,10 +188,10 @@ module.exports = {
http2: useLocalNotLocal ? true : (useLocal ? undefined : true),
https: useLocal ? undefined : {
key: fs.readFileSync(__dirname + '/certs/server-key.pem', 'utf8'),
cert: fs.readFileSync(__dirname + '/certs/server-cert.pem', 'utf8'),
cert: fs.readFileSync(__dirname + '/certs/server-cert.pem', 'utf8')
},
allowedHosts: useLocal ? undefined : [
domain,
domain
],
host: useLocalNotLocal ? localIp : (useLocal ? undefined : '0.0.0.0'),
// host: domain, // '0.0.0.0'
@ -207,8 +207,8 @@ module.exports = { @@ -207,8 +207,8 @@ module.exports = {
},
client: {
overlay: true,
progress: false,
},
progress: false
}
},
plugins: [
@ -216,7 +216,7 @@ module.exports = { @@ -216,7 +216,7 @@ module.exports = {
analyzerMode: 'static',
openAnalyzer: false,
generateStatsFile: false,
defaultSizes: 'gzip',
defaultSizes: 'gzip'
}),
new Dotenv(),
@ -260,36 +260,36 @@ module.exports = { @@ -260,36 +260,36 @@ module.exports = {
keepClosingSlash: true,
minifyJS: true,
minifyCSS: true,
minifyURLs: true,
minifyURLs: true
},
chunks: 'all',
excludeChunks: [],
excludeChunks: []
}),
new HtmlWebpackInjectPreload({
files: [
{
match: /(mtproto).*\.js$/,
attributes: {rel: 'modulepreload'},
},
],
attributes: {rel: 'modulepreload'}
}
]
}),
new MiniCssExtractPlugin({
// Options similar to the same options in webpackOptions.output
// both options are optional
filename: '[name].[contenthash].css',
chunkFilename: '[id].[contenthash].css',
chunkFilename: '[id].[contenthash].css'
}),
new MediaQueryPlugin({
include: [
'style',
'style'
],
queries: {
'only screen and (max-width: 720px)': 'mobile',
'only screen and (min-width: 721px)': 'desktop',
},
'only screen and (min-width: 721px)': 'desktop'
}
}),
new RetryChunkLoadPlugin({
@ -301,13 +301,13 @@ module.exports = { @@ -301,13 +301,13 @@ module.exports = {
// optional value to set the amount of time in milliseconds before trying to load the chunk again. Default is 0
retryDelay: 3000,
// optional value to set the maximum number of retries to load the chunk. Default is 1
maxRetries: 999999,
maxRetries: 999999
// optional list of chunks to which retry script should be injected
// if not set will add retry script to all chunks that have webpack script loading
// chunks: ['chunkName'],
// optional code to be executed in the browser context if after all retries chunk is not loaded.
// if not set - nothing will happen and error will be returned to the chunk loader.
// lastResortScript: "window.location.href='/500.html';",
}),
].filter(Boolean),
})
].filter(Boolean)
};

Loading…
Cancel
Save