Browse Source

Middle ellipsis component

Refactor utils
master
morethanwords 4 years ago
parent
commit
c9abc4868b
  1. 2
      src/components/animationIntersector.ts
  2. 2
      src/components/appMediaViewer.ts
  3. 2
      src/components/appSearch.ts
  4. 2
      src/components/appSelectPeers.ts
  5. 3
      src/components/audio.ts
  6. 2
      src/components/avatar.ts
  7. 2
      src/components/bubbleGroups.ts
  8. 2
      src/components/chat/audio.ts
  9. 2
      src/components/chat/contextMenu.ts
  10. 2
      src/components/chat/input.ts
  11. 2
      src/components/chat/messageRender.ts
  12. 2
      src/components/chat/pinnedContainer.ts
  13. 2
      src/components/chat/search.ts
  14. 2
      src/components/chat/selection.ts
  15. 2
      src/components/dialogsContextMenu.ts
  16. 2
      src/components/emoticonsDropdown/index.ts
  17. 2
      src/components/gifsMasonry.ts
  18. 14
      src/components/groupedLayout.ts
  19. 2
      src/components/horizontalMenu.ts
  20. 142
      src/components/middleEllipsis.ts
  21. 2
      src/components/poll.ts
  22. 2
      src/components/popup.ts
  23. 2
      src/components/popupCreatePoll.ts
  24. 2
      src/components/popupNewMedia.ts
  25. 2
      src/components/popupStickers.ts
  26. 2
      src/components/preloader.ts
  27. 2
      src/components/ripple.ts
  28. 3
      src/components/sidebarLeft/index.ts
  29. 2
      src/components/sidebarLeft/tabs/chatFolders.ts
  30. 2
      src/components/sidebarLeft/tabs/editFolder.ts
  31. 2
      src/components/sidebarLeft/tabs/includedChats.ts
  32. 2
      src/components/sidebarRight/tabs/forward.ts
  33. 2
      src/components/sidebarRight/tabs/gifs.ts
  34. 18
      src/components/sidebarRight/tabs/sharedMedia.ts
  35. 2
      src/components/sidebarRight/tabs/stickers.ts
  36. 14
      src/components/wrappers.ts
  37. 5
      src/emoji/index.ts
  38. 16
      src/helpers/array.ts
  39. 7
      src/helpers/date.ts
  40. 332
      src/helpers/dom.ts
  41. 13
      src/helpers/fileName.ts
  42. 34
      src/helpers/number.ts
  43. 94
      src/helpers/object.ts
  44. 77
      src/helpers/string.ts
  45. 5
      src/lib/appManagers/apiUpdatesManager.ts
  46. 3
      src/lib/appManagers/appChatsManager.ts
  47. 5
      src/lib/appManagers/appDialogsManager.ts
  48. 5
      src/lib/appManagers/appDocsManager.ts
  49. 50
      src/lib/appManagers/appImManager.ts
  50. 5
      src/lib/appManagers/appMessagesManager.ts
  51. 2
      src/lib/appManagers/appPeersManager.ts
  52. 3
      src/lib/appManagers/appPhotosManager.ts
  53. 2
      src/lib/appManagers/appPollsManager.ts
  54. 3
      src/lib/appManagers/appUsersManager.ts
  55. 3
      src/lib/appManagers/appWebPagesManager.ts
  56. 2
      src/lib/mediaPlayer.ts
  57. 2
      src/lib/mtproto/mtprotoworker.ts
  58. 2
      src/lib/mtproto/referenceDatabase.ts
  59. 2
      src/lib/mtproto/serverTimeManager.ts
  60. 3
      src/lib/richtextprocessor.ts
  61. 569
      src/lib/utils.ts
  62. 2
      src/pages/pagePassword.ts
  63. 2
      src/pages/pageSignIn.ts
  64. 2
      src/pages/pagesManager.ts
  65. 2
      src/scss/partials/_chatlist.scss
  66. 6
      src/scss/style.scss

2
src/components/animationIntersector.ts

@ -1,4 +1,4 @@ @@ -1,4 +1,4 @@
import { isInDOM } from "../lib/utils";
import { isInDOM } from "../helpers/dom";
import { RLottiePlayer } from "../lib/lottieLoader";
import { MOUNT_CLASS_TO } from "../lib/mtproto/mtproto_config";
import $rootScope from "../lib/rootScope";

2
src/components/appMediaViewer.ts

@ -11,7 +11,7 @@ import { logger } from "../lib/logger"; @@ -11,7 +11,7 @@ import { logger } from "../lib/logger";
import VideoPlayer from "../lib/mediaPlayer";
import { RichTextProcessor } from "../lib/richtextprocessor";
import $rootScope from "../lib/rootScope";
import { cancelEvent, fillPropertyValue, findUpClassName, generatePathData } from "../lib/utils";
import { cancelEvent, fillPropertyValue, findUpClassName, generatePathData } from "../helpers/dom";
import animationIntersector from "./animationIntersector";
import appMediaPlaybackController from "./appMediaPlaybackController";
import AvatarElement from "./avatar";

2
src/components/appSearch.ts

@ -4,12 +4,12 @@ import appMessagesIDsManager from "../lib/appManagers/appMessagesIDsManager"; @@ -4,12 +4,12 @@ import appMessagesIDsManager from "../lib/appManagers/appMessagesIDsManager";
import appUsersManager from "../lib/appManagers/appUsersManager";
import appPeersManager from '../lib/appManagers/appPeersManager';
import appMessagesManager from "../lib/appManagers/appMessagesManager";
import { escapeRegExp } from "../lib/utils";
import { formatPhoneNumber } from "./misc";
import appChatsManager from "../lib/appManagers/appChatsManager";
import SearchInput from "./searchInput";
import { Peer } from "../layer";
import $rootScope from "../lib/rootScope";
import { escapeRegExp } from "../helpers/string";
export class SearchGroup {
container: HTMLDivElement;

2
src/components/appSelectPeers.ts

@ -5,7 +5,7 @@ import appPeersManager from "../lib/appManagers/appPeersManager"; @@ -5,7 +5,7 @@ import appPeersManager from "../lib/appManagers/appPeersManager";
import appPhotosManager from "../lib/appManagers/appPhotosManager";
import appUsersManager from "../lib/appManagers/appUsersManager";
import $rootScope from "../lib/rootScope";
import { cancelEvent, findUpAttribute, findUpClassName } from "../lib/utils";
import { cancelEvent, findUpAttribute, findUpClassName } from "../helpers/dom";
import Scrollable from "./scrollable";
type PeerType = 'contacts' | 'dialogs';

3
src/components/audio.ts

@ -10,6 +10,7 @@ import mediaSizes from "../helpers/mediaSizes"; @@ -10,6 +10,7 @@ import mediaSizes from "../helpers/mediaSizes";
import { isSafari } from "../helpers/userAgent";
import appMessagesManager from "../lib/appManagers/appMessagesManager";
import $rootScope from "../lib/rootScope";
import './middleEllipsis';
$rootScope.$on('messages_media_read', e => {
const mids = e.detail;
@ -252,7 +253,7 @@ function wrapAudio(doc: MyDocument, audioEl: AudioElement) { @@ -252,7 +253,7 @@ function wrapAudio(doc: MyDocument, audioEl: AudioElement) {
const html = `
<div class="audio-details">
<div class="audio-title">${title}</div>
<div class="audio-title"><middle-ellipsis-element data-font-weight="${audioEl.dataset.fontWeight}">${title}</middle-ellipsis-element></div>
<div class="audio-subtitle">${subtitle}</div>
<div class="audio-time"></div>
</div>`;

2
src/components/avatar.ts

@ -1,7 +1,7 @@ @@ -1,7 +1,7 @@
import appMessagesManager from "../lib/appManagers/appMessagesManager";
import appProfileManager from "../lib/appManagers/appProfileManager";
import $rootScope from "../lib/rootScope";
import { cancelEvent } from "../lib/utils";
import { cancelEvent } from "../helpers/dom";
import AppMediaViewer, { AppMediaViewerAvatar } from "./appMediaViewer";
$rootScope.$on('avatar_update', (e) => {

2
src/components/bubbleGroups.ts

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
import $rootScope from "../lib/rootScope";
import { generatePathData } from "../lib/utils";
import { generatePathData } from "../helpers/dom";
export default class BubbleGroups {
bubblesByGroups: Array<{timestamp: number, fromID: number, mid: number, group: HTMLDivElement[]}> = []; // map to group

2
src/components/chat/audio.ts

@ -2,7 +2,7 @@ import appMessagesManager from "../../lib/appManagers/appMessagesManager"; @@ -2,7 +2,7 @@ import appMessagesManager from "../../lib/appManagers/appMessagesManager";
import appPeersManager from "../../lib/appManagers/appPeersManager";
import { RichTextProcessor } from "../../lib/richtextprocessor";
import $rootScope from "../../lib/rootScope";
import { cancelEvent } from "../../lib/utils";
import { cancelEvent } from "../../helpers/dom";
import appMediaPlaybackController from "../appMediaPlaybackController";
import DivAndCaption from "../divAndCaption";
import { formatDate } from "../wrappers";

2
src/components/chat/contextMenu.ts

@ -5,7 +5,7 @@ import appMessagesManager from "../../lib/appManagers/appMessagesManager"; @@ -5,7 +5,7 @@ import appMessagesManager from "../../lib/appManagers/appMessagesManager";
import appPeersManager from "../../lib/appManagers/appPeersManager";
import appPollsManager, { Poll } from "../../lib/appManagers/appPollsManager";
import $rootScope from "../../lib/rootScope";
import { cancelEvent, cancelSelection, findUpClassName } from "../../lib/utils";
import { cancelEvent, cancelSelection, findUpClassName } from "../../helpers/dom";
import ButtonMenu, { ButtonMenuItemOptions } from "../buttonMenu";
import { attachContextMenuListener, openBtnMenu, positionMenu } from "../misc";
import PopupDeleteMessages from "../popupDeleteMessages";

2
src/components/chat/input.ts

@ -11,7 +11,7 @@ import apiManager from "../../lib/mtproto/mtprotoworker"; @@ -11,7 +11,7 @@ import apiManager from "../../lib/mtproto/mtprotoworker";
import opusDecodeController from "../../lib/opusDecodeController";
import { RichTextProcessor } from "../../lib/richtextprocessor";
import $rootScope from '../../lib/rootScope';
import { cancelEvent, findUpClassName, getRichValue } from "../../lib/utils";
import { cancelEvent, findUpClassName, getRichValue } from "../../helpers/dom";
import ButtonMenu, { ButtonMenuItemOptions } from '../buttonMenu';
import emoticonsDropdown from "../emoticonsDropdown";
import PopupCreatePoll from "../popupCreatePoll";

2
src/components/chat/messageRender.ts

@ -1,6 +1,6 @@ @@ -1,6 +1,6 @@
import { getFullDate } from "../../helpers/date";
import { formatNumber } from "../../helpers/number";
import RichTextProcessor from "../../lib/richtextprocessor";
import { formatNumber } from "../../lib/utils";
type Message = any;

2
src/components/chat/pinnedContainer.ts

@ -1,6 +1,6 @@ @@ -1,6 +1,6 @@
import mediaSizes from "../../helpers/mediaSizes";
import appImManager from "../../lib/appManagers/appImManager";
import { cancelEvent } from "../../lib/utils";
import { cancelEvent } from "../../helpers/dom";
import DivAndCaption from "../divAndCaption";
import { ripple } from "../ripple";

2
src/components/chat/search.ts

@ -1,6 +1,6 @@ @@ -1,6 +1,6 @@
import appImManager from "../../lib/appManagers/appImManager";
import $rootScope from "../../lib/rootScope";
import { cancelEvent, whichChild, findUpTag } from "../../lib/utils";
import { cancelEvent, whichChild, findUpTag } from "../../helpers/dom";
import AppSearch, { SearchGroup } from "../appSearch";
import PopupDatePicker from "../popupDatepicker";
import { ripple } from "../ripple";

2
src/components/chat/selection.ts

@ -1,7 +1,7 @@ @@ -1,7 +1,7 @@
import { isTouchSupported } from "../../helpers/touchSupport";
import type { AppImManager } from "../../lib/appManagers/appImManager";
import type { AppMessagesManager } from "../../lib/appManagers/appMessagesManager";
import { cancelEvent, cancelSelection, findUpClassName, getSelectedText } from "../../lib/utils";
import { cancelEvent, cancelSelection, findUpClassName, getSelectedText } from "../../helpers/dom";
import Button from "../button";
import ButtonIcon from "../buttonIcon";
import CheckboxField from "../checkbox";

2
src/components/dialogsContextMenu.ts

@ -4,7 +4,7 @@ import appImManager from "../lib/appManagers/appImManager"; @@ -4,7 +4,7 @@ import appImManager from "../lib/appManagers/appImManager";
import appMessagesManager from "../lib/appManagers/appMessagesManager";
import appPeersManager from "../lib/appManagers/appPeersManager";
import $rootScope from "../lib/rootScope";
import { findUpTag } from "../lib/utils";
import { findUpTag } from "../helpers/dom";
import { parseMenuButtonsTo, positionMenu, openBtnMenu } from "./misc";
import { PopupButton } from "./popup";
import PopupPeer from "./popupPeer";

2
src/components/emoticonsDropdown/index.ts

@ -3,7 +3,7 @@ import appChatsManager from "../../lib/appManagers/appChatsManager"; @@ -3,7 +3,7 @@ import appChatsManager from "../../lib/appManagers/appChatsManager";
import appImManager from "../../lib/appManagers/appImManager";
import { MOUNT_CLASS_TO } from "../../lib/mtproto/mtproto_config";
import $rootScope from "../../lib/rootScope";
import { findUpClassName, findUpTag, whichChild } from "../../lib/utils";
import { findUpClassName, findUpTag, whichChild } from "../../helpers/dom";
import animationIntersector from "../animationIntersector";
import { horizontalMenu } from "../horizontalMenu";
import LazyLoadQueue, { LazyLoadQueueIntersector } from "../lazyLoadQueue";

2
src/components/gifsMasonry.ts

@ -1,4 +1,4 @@ @@ -1,4 +1,4 @@
import { calcImageInBox } from "../lib/utils";
import { calcImageInBox } from "../helpers/dom";
import appDocsManager, {MyDocument} from "../lib/appManagers/appDocsManager";
import { wrapVideo } from "./wrappers";
import { renderImageFromUrl } from "./misc";

14
src/components/groupedLayout.ts

@ -5,6 +5,9 @@ For license and copyright information please follow this link: @@ -5,6 +5,9 @@ For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
import { accumulate } from "../helpers/array";
import { clamp } from "../helpers/number";
type Size = {w: number, h: number};
export type GroupMediaLayout = {
geometry: {
@ -27,13 +30,6 @@ export const RectPart = { @@ -27,13 +30,6 @@ export const RectPart = {
Left: 8
};
let accumulate = (arr: number[], initialValue: number) => arr.reduce((acc, value) => acc + value, initialValue);
// https://github.com/telegramdesktop/tdesktop/blob/74d848311b31ef0eb6d2c43a4d30ade8f1d2d9fb/Telegram/SourceFiles/core/utils.h#L128
function snap<T>(v: T, _min: T, _max: T): T {
return (v < _min) ? _min : ((v > _max) ? _max : v);
}
// https://github.com/telegramdesktop/tdesktop/blob/4669c07dc5335cbf4795bbbe5b0ab7c007b9aee2/Telegram/SourceFiles/ui/grouped_layout.cpp
export class Layouter {
private count: number;
@ -324,8 +320,8 @@ class ComplexLayouter { @@ -324,8 +320,8 @@ class ComplexLayouter {
const kMinRatio = 0.6667;
return ratios.map(ratio => {
return averageRatio > 1.1
? snap(ratio, 1., kMaxRatio)
: snap(ratio, kMinRatio, 1.);
? clamp(ratio, 1., kMaxRatio)
: clamp(ratio, kMinRatio, 1.);
});
}

2
src/components/horizontalMenu.ts

@ -1,4 +1,4 @@ @@ -1,4 +1,4 @@
import { findUpTag, whichChild } from "../lib/utils";
import { findUpTag, whichChild } from "../helpers/dom";
import Transition from "./transition";
export function horizontalMenu(tabs: HTMLElement, content: HTMLElement, onClick?: (id: number, tabContent: HTMLDivElement) => void, onTransitionEnd?: () => void, transitionTime = 250) {

142
src/components/middleEllipsis.ts

@ -0,0 +1,142 @@ @@ -0,0 +1,142 @@
// Thanks to https://stackoverflow.com/a/49349813
import { clamp } from "../helpers/number";
/**
* Attibute modifier to create middle ellipsis
* When the attribute value is left blank the ellipsis will be in the middle
* When positive the attribute value will be used as a percentage
* When negative the attribute value will be used as character index counted from the end
* @example
* <div data-middle-ellipsis>A Javascript solution to middle ellipsis</div>
* <div data-middle-ellipsis="20">A Javascript solution to middle ellipsis</div>
* <div data-middle-ellipsis="-3">A Javascript solution to middle ellipsis</div>
*/
const attributeName = 'data-middle-ellipsis';
const ellipsis = '…';
const map: Map<HTMLElement, {
text: string,
textLength: number,
from: number,
multiplier: number,
font: string,
textWidth: number,
elementWidth: number
}> = new Map();
const testQueue: Set<HTMLElement> = new Set();
const fontFamily = 'Roboto, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif';
const fontSize = '16px';
let timeoutId: number;
const setTestQueue = () => {
cancelAnimationFrame(timeoutId);
timeoutId = window.requestAnimationFrame(testQueueElements);
};
const testQueueElements = () => {
testQueue.forEach(testElement);
testQueue.clear();
};
window.addEventListener('resize', () => {
(Array.from(document.querySelectorAll(`[${attributeName}]`)) as HTMLElement[]).forEach(el => testQueue.add(el));
setTestQueue();
}, {capture: true, passive: true});
const testElement = (elm: HTMLElement) => {
//const perf = performance.now();
// do not recalculate variables a second time
const mapped = map.get(elm);
let {text, textLength, from, multiplier, font, textWidth, elementWidth} = mapped || {};
// first time
if(!mapped) {
text = elm.textContent;
textLength = text.length;
from = parseFloat(elm.getAttribute(attributeName)) || 50;
multiplier = from > 0 && from / 100;
//const perf = performance.now();
font = `${elm.dataset.fontWeight || 400} ${fontSize} ${fontFamily}`;
/* const computedStyle = window.getComputedStyle(elm, null);
font = `${computedStyle.getPropertyValue('font-weight')} ${computedStyle.getPropertyValue('font-size')} ${computedStyle.getPropertyValue('font-family')}`; */
//console.log('testMiddleEllipsis get computed style:', performance.now() - perf, font);
textWidth = getTextWidth(text, font);
//const perf = performance.now();
elementWidth = elm.offsetWidth;
//console.log('testMiddleEllipsis get offsetWidth:', performance.now() - perf, font);
map.set(elm, {text, textLength, from, multiplier, font, textWidth, elementWidth});
}
const {offsetWidth} = elm;
const widthChanged = !mapped || elementWidth !== offsetWidth;
mapped && widthChanged && (mapped.elementWidth = elementWidth = offsetWidth);
if(widthChanged) {
if(textWidth > elementWidth) {
elm.setAttribute('title', text);
let smallerText = text;
let smallerWidth = elementWidth;
while(smallerText.length > 3) {
let smallerTextLength = smallerText.length;
const half = multiplier &&
clamp(multiplier * smallerTextLength << 0, 1, smallerTextLength - 2) ||
Math.max(smallerTextLength + from - 1, 1);
const half1 = smallerText.substr(0, half).replace(/\s*$/,'');
const half2 = smallerText.substr(half + 1).replace(/^\s*/,'');
smallerText = half1 + half2;
smallerWidth = getTextWidth(smallerText + ellipsis, font);
if(smallerWidth < elementWidth) {
elm.textContent = half1 + ellipsis + half2;
break;
}
}
} else {
elm.removeAttribute('title');
}
}
//console.log('testMiddleEllipsis for element:', elm, performance.now() - perf);
};
let context: CanvasRenderingContext2D;
/**
* Get the text width
* @param {string} text
* @param {string} font
*/
function getTextWidth(text: string, font: string) {
//const perf = performance.now();
if(!context) {
const canvas = document.createElement('canvas');
context = canvas.getContext('2d');
context.font = font;
}
//context.font = font;
const metrics = context.measureText(text);
//console.log('getTextWidth perf:', performance.now() - perf);
return metrics.width;
}
export class MiddleEllipsisElement extends HTMLElement {
constructor() {
super();
if(this.getAttribute('data-middle-ellipsis') === null) {
this.setAttribute('data-middle-ellipsis', '');
}
}
connectedCallback() {
testQueue.add(this);
setTestQueue();
//testElement(this);
}
disconnectedCallback() {
map.delete(this);
}
}
customElements.define("middle-ellipsis-element", MiddleEllipsisElement);

2
src/components/poll.ts

@ -5,7 +5,7 @@ import appPollsManager, { Poll, PollResults } from "../lib/appManagers/appPollsM @@ -5,7 +5,7 @@ import appPollsManager, { Poll, PollResults } from "../lib/appManagers/appPollsM
import serverTimeManager from "../lib/mtproto/serverTimeManager";
import { RichTextProcessor } from "../lib/richtextprocessor";
import $rootScope from "../lib/rootScope";
import { cancelEvent, findUpClassName } from "../lib/utils";
import { cancelEvent, findUpClassName } from "../helpers/dom";
import { ripple } from "./ripple";
import appSidebarRight from "./sidebarRight";

2
src/components/popup.ts

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
import $rootScope from "../lib/rootScope";
import { cancelEvent, findUpClassName } from "../lib/utils";
import { cancelEvent, findUpClassName } from "../helpers/dom";
import { ripple } from "./ripple";
export class PopupElement {

2
src/components/popupCreatePoll.ts

@ -2,7 +2,7 @@ import appMessagesManager from "../lib/appManagers/appMessagesManager"; @@ -2,7 +2,7 @@ import appMessagesManager from "../lib/appManagers/appMessagesManager";
import appPeersManager from "../lib/appManagers/appPeersManager";
import appPollsManager, { Poll } from "../lib/appManagers/appPollsManager";
import $rootScope from "../lib/rootScope";
import { findUpTag, whichChild } from "../lib/utils";
import { findUpTag, whichChild } from "../helpers/dom";
import CheckboxField from "./checkbox";
import InputField from "./inputField";
import { PopupElement } from "./popup";

2
src/components/popupNewMedia.ts

@ -1,7 +1,7 @@ @@ -1,7 +1,7 @@
import { isTouchSupported } from "../helpers/touchSupport";
import appImManager from "../lib/appManagers/appImManager";
import appMessagesManager from "../lib/appManagers/appMessagesManager";
import { calcImageInBox } from "../lib/utils";
import { calcImageInBox } from "../helpers/dom";
import { Layouter, RectPart } from "./groupedLayout";
import InputField from "./inputField";
import { PopupElement } from "./popup";

2
src/components/popupStickers.ts

@ -6,7 +6,7 @@ import { wrapSticker } from "./wrappers"; @@ -6,7 +6,7 @@ import { wrapSticker } from "./wrappers";
import LazyLoadQueue from "./lazyLoadQueue";
import { putPreloader } from "./misc";
import animationIntersector from "./animationIntersector";
import { findUpClassName } from "../lib/utils";
import { findUpClassName } from "../helpers/dom";
import appImManager from "../lib/appManagers/appImManager";
import { StickerSet } from "../layer";
import mediaSizes from "../helpers/mediaSizes";

2
src/components/preloader.ts

@ -1,4 +1,4 @@ @@ -1,4 +1,4 @@
import { isInDOM, cancelEvent } from "../lib/utils";
import { isInDOM, cancelEvent } from "../helpers/dom";
import { CancellablePromise } from "../helpers/cancellablePromise";
export default class ProgressivePreloader {

2
src/components/ripple.ts

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
import {isTouchSupported} from "../helpers/touchSupport";
import { findUpClassName } from "../lib/utils";
import { findUpClassName } from "../helpers/dom";
let rippleClickID = 0;
export function ripple(elem: HTMLElement, callback: (id: number) => Promise<boolean | void> = () => Promise.resolve(), onEnd: (id: number) => void = null) {

3
src/components/sidebarLeft/index.ts

@ -1,4 +1,5 @@ @@ -1,4 +1,5 @@
//import { logger } from "../polyfill";
import { formatNumber } from "../../helpers/number";
import appChatsManager from "../../lib/appManagers/appChatsManager";
import appDialogsManager from "../../lib/appManagers/appDialogsManager";
import appImManager from "../../lib/appManagers/appImManager";
@ -7,7 +8,7 @@ import appStateManager from "../../lib/appManagers/appStateManager"; @@ -7,7 +8,7 @@ import appStateManager from "../../lib/appManagers/appStateManager";
import appUsersManager from "../../lib/appManagers/appUsersManager";
import { MOUNT_CLASS_TO } from "../../lib/mtproto/mtproto_config";
import $rootScope from "../../lib/rootScope";
import { findUpClassName, findUpTag, formatNumber } from "../../lib/utils";
import { findUpClassName, findUpTag } from "../../helpers/dom";
import AppSearch, { SearchGroup } from "../appSearch";
import AvatarElement from "../avatar";
import { parseMenuButtonsTo } from "../misc";

2
src/components/sidebarLeft/tabs/chatFolders.ts

@ -4,7 +4,7 @@ import apiManager from "../../../lib/mtproto/mtprotoworker"; @@ -4,7 +4,7 @@ import apiManager from "../../../lib/mtproto/mtprotoworker";
import appMessagesManager, { MyDialogFilter } from "../../../lib/appManagers/appMessagesManager";
import { RichTextProcessor } from "../../../lib/richtextprocessor";
import appPeersManager from "../../../lib/appManagers/appPeersManager";
import { cancelEvent } from "../../../lib/utils";
import { cancelEvent } from "../../../helpers/dom";
import appSidebarLeft from "..";
import { ripple } from "../../ripple";
import { toast } from "../../toast";

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

@ -1,8 +1,8 @@ @@ -1,8 +1,8 @@
import appSidebarLeft, { AppSidebarLeft } from "..";
import { deepEqual, copy } from "../../../helpers/object";
import appDialogsManager from "../../../lib/appManagers/appDialogsManager";
import appMessagesManager, { MyDialogFilter as DialogFilter } from "../../../lib/appManagers/appMessagesManager";
import lottieLoader, { RLottiePlayer } from "../../../lib/lottieLoader";
import { copy, deepEqual } from "../../../lib/utils";
import { parseMenuButtonsTo } from "../../misc";
import { ripple } from "../../ripple";
import { SliderTab } from "../../slider";

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

@ -4,9 +4,9 @@ import appSidebarLeft, { AppSidebarLeft } from ".."; @@ -4,9 +4,9 @@ import appSidebarLeft, { AppSidebarLeft } from "..";
import appDialogsManager from "../../../lib/appManagers/appDialogsManager";
import appPeersManager from "../../../lib/appManagers/appPeersManager";
import appUsersManager from "../../../lib/appManagers/appUsersManager";
import { copy } from "../../../lib/utils";
import { MyDialogFilter as DialogFilter } from "../../../lib/appManagers/appMessagesManager";
import $rootScope from "../../../lib/rootScope";
import { copy } from "../../../helpers/object";
export default class AppIncludedChatsTab implements SliderTab {
public container: HTMLElement;

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

@ -1,4 +1,4 @@ @@ -1,4 +1,4 @@
import appSidebarRight, { AppSidebarRight } from "..";
import appSidebarRight from "..";
import appMessagesManager from "../../../lib/appManagers/appMessagesManager";
import AppSelectPeers from "../../appSelectPeers";
import { putPreloader } from "../../misc";

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

@ -6,7 +6,7 @@ import appSidebarRight, { AppSidebarRight } from ".."; @@ -6,7 +6,7 @@ import appSidebarRight, { AppSidebarRight } from "..";
import appUsersManager from "../../../lib/appManagers/appUsersManager";
import appInlineBotsManager, { AppInlineBotsManager } from "../../../lib/appManagers/AppInlineBotsManager";
import GifsMasonry from "../../gifsMasonry";
import { findUpClassName } from "../../../lib/utils";
import { findUpClassName } from "../../../helpers/dom";
import appImManager from "../../../lib/appManagers/appImManager";
import type { MyDocument } from "../../../lib/appManagers/appDocsManager";
import mediaSizes from "../../../helpers/mediaSizes";

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

@ -1,3 +1,4 @@ @@ -1,3 +1,4 @@
import { limitSymbols } from "../../../helpers/string";
import appImManager from "../../../lib/appManagers/appImManager";
import appMessagesManager from "../../../lib/appManagers/appMessagesManager";
import appPeersManager from "../../../lib/appManagers/appPeersManager";
@ -7,7 +8,6 @@ import appUsersManager from "../../../lib/appManagers/appUsersManager"; @@ -7,7 +8,6 @@ import appUsersManager from "../../../lib/appManagers/appUsersManager";
import { logger } from "../../../lib/logger";
import { RichTextProcessor } from "../../../lib/richtextprocessor";
import $rootScope from "../../../lib/rootScope";
import { limitSymbols } from "../../../lib/utils";
import AppMediaViewer from "../../appMediaViewer";
import AvatarElement from "../../avatar";
import { horizontalMenu } from "../../horizontalMenu";
@ -478,9 +478,10 @@ export default class AppSharedMediaTab implements SliderTab { @@ -478,9 +478,10 @@ export default class AppSharedMediaTab implements SliderTab {
break;
}
case 'inputMessagesFilterMusic':
case 'inputMessagesFilterDocument': {
for(let message of messages) {
let div = wrapDocument(message.media.document, true, false, message.mid);
for(const message of messages) {
const div = wrapDocument(message.media.document, true, false, message.mid, 400);
div.dataset.mid = '' + message.mid;
elemsToAppend.push(div);
}
@ -592,16 +593,7 @@ export default class AppSharedMediaTab implements SliderTab { @@ -592,16 +593,7 @@ export default class AppSharedMediaTab implements SliderTab {
break;
}
case 'inputMessagesFilterMusic': {
for(let message of messages) {
let div = wrapAudio(message.media.document, true, message.mid);
div.dataset.mid = '' + message.mid;
elemsToAppend.push(div);
}
break;
}
default:
//console.warn('death is my friend', messages);
break;

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

@ -2,7 +2,7 @@ import { SliderTab } from "../../slider"; @@ -2,7 +2,7 @@ import { SliderTab } from "../../slider";
import SearchInput from "../../searchInput";
import Scrollable from "../../scrollable";
import LazyLoadQueue from "../../lazyLoadQueue";
import { findUpClassName } from "../../../lib/utils";
import { findUpClassName } from "../../../helpers/dom";
import appImManager from "../../../lib/appManagers/appImManager";
import appStickersManager from "../../../lib/appManagers/appStickersManager";
import PopupStickers from "../../popupStickers";

14
src/components/wrappers.ts

@ -1,7 +1,9 @@ @@ -1,7 +1,9 @@
import { getEmojiToneIndex } from '../emoji';
import { readBlobAsText } from '../helpers/blob';
import { deferredPromise } from '../helpers/cancellablePromise';
import { months } from '../helpers/date';
import mediaSizes from '../helpers/mediaSizes';
import { formatBytes } from '../helpers/number';
import { isAppleMobile, isSafari } from '../helpers/userAgent';
import { PhotoSize } from '../layer';
import appDocsManager, { MyDocument } from "../lib/appManagers/appDocsManager";
@ -10,7 +12,7 @@ import appMessagesManager from '../lib/appManagers/appMessagesManager'; @@ -10,7 +12,7 @@ import appMessagesManager from '../lib/appManagers/appMessagesManager';
import appPhotosManager, { MyPhoto } from '../lib/appManagers/appPhotosManager';
import LottieLoader from '../lib/lottieLoader';
import VideoPlayer from '../lib/mediaPlayer';
import { cancelEvent, formatBytes, getEmojiToneIndex, isInDOM } from "../lib/utils";
import { isInDOM } from "../helpers/dom";
import webpWorkerController from '../lib/webp/webpWorkerController';
import animationIntersector from './animationIntersector';
import appMediaPlaybackController from './appMediaPlaybackController';
@ -21,6 +23,7 @@ import LazyLoadQueue from './lazyLoadQueue'; @@ -21,6 +23,7 @@ import LazyLoadQueue from './lazyLoadQueue';
import { renderImageFromUrl } from './misc';
import PollElement from './poll';
import ProgressivePreloader from './preloader';
import './middleEllipsis';
const MAX_VIDEO_AUTOPLAY_SIZE = 50 * 1024 * 1024; // 50 MB
@ -316,9 +319,11 @@ export const formatDate = (timestamp: number, monthShort = false, withYear = tru @@ -316,9 +319,11 @@ export const formatDate = (timestamp: number, monthShort = false, withYear = tru
return str + ' at ' + date.getHours() + ':' + ('0' + date.getMinutes()).slice(-2);
};
export function wrapDocument(doc: MyDocument, withTime = false, uploading = false, mid?: number): HTMLElement {
export function wrapDocument(doc: MyDocument, withTime = false, uploading = false, mid?: number, fontWeight = 500): HTMLElement {
if(doc.type == 'audio' || doc.type == 'voice') {
return wrapAudio(doc, withTime, mid);
const audioElement = wrapAudio(doc, withTime, mid);
audioElement.dataset.fontWeight = '' + fontWeight;
return audioElement;
}
let extSplitted = doc.file_name ? doc.file_name.split('.') : '';
@ -347,6 +352,7 @@ export function wrapDocument(doc: MyDocument, withTime = false, uploading = fals @@ -347,6 +352,7 @@ export function wrapDocument(doc: MyDocument, withTime = false, uploading = fals
icoDiv.innerText = ext;
}
//let fileName = stringMiddleOverflow(doc.file_name || 'Unknown.file', 26);
let fileName = doc.file_name || 'Unknown.file';
let size = formatBytes(doc.size);
@ -356,7 +362,7 @@ export function wrapDocument(doc: MyDocument, withTime = false, uploading = fals @@ -356,7 +362,7 @@ export function wrapDocument(doc: MyDocument, withTime = false, uploading = fals
docDiv.innerHTML = `
${!uploading ? `<div class="document-download"><div class="tgico-download"></div></div>` : ''}
<div class="document-name">${fileName}</div>
<div class="document-name"><middle-ellipsis-element data-font-weight="${fontWeight}">${fileName}</middle-ellipsis-element></div>
<div class="document-size">${size}</div>
`;

5
src/emoji/index.ts

@ -33,4 +33,9 @@ export function toCodePoints(unicodeSurrogates: string): Array<string> { @@ -33,4 +33,9 @@ export function toCodePoints(unicodeSurrogates: string): Array<string> {
}
return points;
}
export function getEmojiToneIndex(input: string) {
let match = input.match(/[\uDFFB-\uDFFF]/);
return match ? 5 - (57343 - match[0].charCodeAt(0)) : 0;
}

16
src/helpers/array.ts

@ -0,0 +1,16 @@ @@ -0,0 +1,16 @@
import { copy } from "./object";
export function listMergeSorted(list1: any[] = [], list2: any[] = []) {
const result = copy(list1);
const minID = list1.length ? list1[list1.length - 1] : 0xFFFFFFFF;
for(let i = 0; i < list2.length; i++) {
if(list2[i] < minID) {
result.push(list2[i]);
}
}
return result;
}
export const accumulate = (arr: number[], initialValue: number) => arr.reduce((acc, value) => acc + value, initialValue);

7
src/helpers/date.ts

@ -34,4 +34,9 @@ export const formatDateAccordingToToday = (time: Date) => { @@ -34,4 +34,9 @@ export const formatDateAccordingToToday = (time: Date) => {
export const getFullDate = (date: Date) => {
return date.getDate() + ' ' + months[date.getMonth()] + ' ' + date.getFullYear() +
', ' + ('0' + date.getHours()).slice(-2) + ':' + ('0' + date.getMinutes()).slice(-2) + ':' + ('0' + date.getSeconds()).slice(-2);
};
};
export function tsNow(seconds?: true) {
const t = Date.now();
return seconds ? Math.floor(t / 1000) : t;
}

332
src/helpers/dom.ts

@ -0,0 +1,332 @@ @@ -0,0 +1,332 @@
/* export function isInDOM(element: Element, parentNode?: HTMLElement): boolean {
if(!element) {
return false;
}
parentNode = parentNode || document.body;
if(element == parentNode) {
return true;
}
return isInDOM(element.parentNode as HTMLElement, parentNode);
} */
export function isInDOM(element: Element): boolean {
return element?.isConnected;
}
/* export function checkDragEvent(e: any) {
if(!e || e.target && (e.target.tagName == 'IMG' || e.target.tagName == 'A')) return false
if(e.dataTransfer && e.dataTransfer.types) {
for(var i = 0; i < e.dataTransfer.types.length; i++) {
if(e.dataTransfer.types[i] == 'Files') {
return true;
}
}
} else {
return true;
}
return false;
} */
export function cancelEvent(event: Event) {
event = event || window.event;
if(event) {
// @ts-ignore
event = event.originalEvent || event;
try {
if(event.stopPropagation) event.stopPropagation();
if(event.preventDefault) event.preventDefault();
event.returnValue = false;
event.cancelBubble = true;
} catch(err) {}
}
return false;
}
export function getRichValue(field: any) {
if(!field) {
return '';
}
var lines: string[] = [];
var line: string[] = [];
getRichElementValue(field, lines, line);
if (line.length) {
lines.push(line.join(''));
}
var value = lines.join('\n');
value = value.replace(/\u00A0/g, ' ');
return value;
}
export function placeCaretAtEnd(el: HTMLElement) {
el.focus();
if(typeof window.getSelection != "undefined" && typeof document.createRange != "undefined") {
var range = document.createRange();
range.selectNodeContents(el);
range.collapse(false);
var sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(range);
// @ts-ignore
} else if(typeof document.body.createTextRange != "undefined") {
// @ts-ignore
var textRange = document.body.createTextRange();
textRange.moveToElementText(el);
textRange.collapse(false);
textRange.select();
}
}
export function getRichElementValue(node: any, lines: string[], line: string[], selNode?: Node, selOffset?: number) {
if(node.nodeType == 3) { // TEXT
if(selNode === node) {
var value = node.nodeValue
line.push(value.substr(0, selOffset) + '\x01' + value.substr(selOffset))
} else {
line.push(node.nodeValue)
}
return
}
if (node.nodeType != 1) { // NON-ELEMENT
return
}
var isSelected = (selNode === node)
var isBlock = node.tagName == 'DIV' || node.tagName == 'P'
var curChild
if(isBlock && line.length || node.tagName == 'BR') {
lines.push(line.join(''))
line.splice(0, line.length)
} else if(node.tagName == 'IMG') {
if(node.alt) {
line.push(node.alt);
}
}
if(isSelected && !selOffset) {
line.push('\x01');
}
var curChild = node.firstChild;
while(curChild) {
getRichElementValue(curChild, lines, line, selNode, selOffset);
curChild = curChild.nextSibling;
}
if(isSelected && selOffset) {
line.push('\x01');
}
if(isBlock && line.length) {
lines.push(line.join(''));
line.splice(0, line.length);
}
}
/* if (Config.Modes.animations &&
typeof window.requestAnimationFrame == 'function') {
window.onAnimationFrameCallback = function (cb) {
return (function () {
window.requestAnimationFrame(cb)
})
}
} else {
window.onAnimationFrameCallback = function (cb) {
return cb
}
} */
// generate a path's arc data parameter
// http://www.w3.org/TR/SVG/paths.html#PathDataEllipticalArcCommands
var arcParameter = function(rx: number, ry: number, xAxisRotation: number, largeArcFlag: number, sweepFlag: number, x: number, y: number) {
return [rx, ',', ry, ' ',
xAxisRotation, ' ',
largeArcFlag, ',',
sweepFlag, ' ',
x, ',', y ].join('');
};
export function generatePathData(x: number, y: number, width: number, height: number, tl: number, tr: number, br: number, bl: number) {
const data: string[] = [];
// start point in top-middle of the rectangle
data.push('M' + (x + width / 2) + ',' + y);
// next we go to the right
data.push('H' + (x + width - tr));
if(tr > 0) {
// now we draw the arc in the top-right corner
data.push('A' + arcParameter(tr, tr, 0, 0, 1, (x + width), (y + tr)));
}
// next we go down
data.push('V' + (y + height - br));
if(br > 0) {
// now we draw the arc in the lower-right corner
data.push('A' + arcParameter(br, br, 0, 0, 1, (x + width - br), (y + height)));
}
// now we go to the left
data.push('H' + (x + bl));
if(bl > 0) {
// now we draw the arc in the lower-left corner
data.push('A' + arcParameter(bl, bl, 0, 0, 1, (x + 0), (y + height - bl)));
}
// next we go up
data.push('V' + (y + tl));
if(tl > 0) {
// now we draw the arc in the top-left corner
data.push('A' + arcParameter(tl, tl, 0, 0, 1, (x + tl), (y + 0)));
}
// and we close the path
data.push('Z');
return data.join(' ');
};
//export function findUpClassName<T>(el: any, className: string): T;
export function findUpClassName(el: any, className: string): HTMLElement {
return el.closest('.' + className);
/* if(el.classList.contains(className)) return el; // 03.02.2020
while(el.parentElement) {
el = el.parentElement;
if(el.classList.contains(className))
return el;
}
return null; */
}
export function findUpTag(el: any, tag: string): HTMLElement {
return el.closest(tag);
/* if(el.tagName == tag) return el; // 03.02.2020
while(el.parentElement) {
el = el.parentElement;
if(el.tagName === tag)
return el;
}
return null; */
}
export function findUpAttribute(el: any, attribute: string): HTMLElement {
return el.closest(`[${attribute}]`);
/* if(el.getAttribute(attribute) != null) return el; // 03.02.2020
while(el.parentElement) {
el = el.parentElement;
if(el.getAttribute(attribute) != null)
return el;
}
return null; */
}
export function whichChild(elem: Node) {
if(!elem.parentNode) {
return -1;
}
let i = 0;
// @ts-ignore
while((elem = elem.previousElementSibling) != null) ++i;
return i;
};
export function fillPropertyValue(str: string) {
let splitted = str.split(' ');
if(splitted.length != 4) {
if(!splitted[0]) splitted[0] = '0px';
for(let i = splitted.length; i < 4; ++i) {
splitted[i] = splitted[i % 2] || splitted[0] || '0px';
}
}
return splitted;
}
export function calcImageInBox(imageW: number, imageH: number, boxW: number, boxH: number, noZoom?: boolean) {
if(imageW < boxW && imageH < boxH) {
return {w: imageW, h: imageH};
}
var boxedImageW = boxW;
var boxedImageH = boxH;
if((imageW / imageH) > (boxW / boxH)) {
boxedImageH = (imageH * boxW / imageW) | 0;
} else {
boxedImageW = (imageW * boxH / imageH) | 0;
if(boxedImageW > boxW) {
boxedImageH = (boxedImageH * boxW / boxedImageW) | 0;
boxedImageW = boxW;
}
}
// if (Config.Navigator.retina) {
// imageW = Math.floor(imageW / 2)
// imageH = Math.floor(imageH / 2)
// }
if(noZoom && boxedImageW >= imageW && boxedImageH >= imageH) {
boxedImageW = imageW;
boxedImageH = imageH;
}
return {w: boxedImageW, h: boxedImageH};
}
export function positionElementByIndex(element: HTMLElement, container: HTMLElement, pos: number) {
const prevPos = whichChild(element);
if(prevPos == pos) {
return false;
} else if(prevPos != -1 && prevPos < pos) { // was higher
pos += 1;
}
if(container.childElementCount > pos) {
container.insertBefore(element, container.children[pos]);
} else {
container.append(element);
}
return true;
}
export function cancelSelection() {
if(window.getSelection) {
if(window.getSelection().empty) { // Chrome
window.getSelection().empty();
} else if(window.getSelection().removeAllRanges) { // Firefox
window.getSelection().removeAllRanges();
}
// @ts-ignore
} else if(document.selection) { // IE?
// @ts-ignore
document.selection.empty();
}
}
//(window as any).splitStringByLength = splitStringByLength;
export function getSelectedText(): string {
if(window.getSelection) {
return window.getSelection().toString();
// @ts-ignore
} else if(document.selection) {
// @ts-ignore
return document.selection.createRange().text;
}
return '';
}

13
src/helpers/fileName.ts

@ -1,4 +1,5 @@ @@ -1,4 +1,5 @@
import { InputFileLocation, FileLocation } from "../layer";
import type { InputFileLocation, FileLocation } from "../layer";
import type { DownloadOptions } from "../lib/mtproto/apiFileManager";
export function getFileNameByLocation(location: InputFileLocation | FileLocation, options?: Partial<{
fileName: string
@ -25,4 +26,14 @@ export function getFileNameByLocation(location: InputFileLocation | FileLocation @@ -25,4 +26,14 @@ export function getFileNameByLocation(location: InputFileLocation | FileLocation
return '';
}
}
}
export type FileURLType = 'photo' | 'thumb' | 'document' | 'stream' | 'download';
export function getFileURL(type: FileURLType, options: DownloadOptions) {
//console.log('getFileURL', location);
//const perf = performance.now();
const encoded = encodeURIComponent(JSON.stringify(options));
//console.log('getFileURL encode:', performance.now() - perf, encoded);
return '/' + type + '/' + encoded;
}

34
src/helpers/number.ts

@ -0,0 +1,34 @@ @@ -0,0 +1,34 @@
export function numberWithCommas(x: number) {
const parts = x.toString().split(".");
parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ",");
return parts.join(".");
}
export function formatBytes(bytes: number, decimals = 2) {
if (bytes === 0) return '0 Bytes';
const k = 1024;
const dm = decimals < 0 ? 0 : decimals;
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i];
}
export function formatNumber(bytes: number, decimals = 2) {
if(bytes === 0) return '0';
const k = 1000;
const dm = decimals < 0 ? 0 : decimals;
const sizes = ['', 'K', 'M', 'B', 'T'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + sizes[i];
}
export function clamp(v: number, min: number, max: number): number {
return v < min ? min : ((v > max) ? max : v);
}

94
src/helpers/object.ts

@ -0,0 +1,94 @@ @@ -0,0 +1,94 @@
export function copy<T>(obj: T): T {
//in case of premitives
if(obj === null || typeof(obj) !== "object") {
return obj;
}
//date objects should be
if(obj instanceof Date) {
return new Date(obj.getTime()) as any;
}
//handle Array
if(Array.isArray(obj)) {
const clonedArr: T = obj.map(el => copy(el)) as any as T;
return clonedArr;
}
//lastly, handle objects
// @ts-ignore
let clonedObj = new obj.constructor();
for(var prop in obj){
if(obj.hasOwnProperty(prop)) {
clonedObj[prop] = copy(obj[prop]);
}
}
return clonedObj;
}
export function deepEqual(x: any, y: any): boolean {
const ok = Object.keys, tx = typeof x, ty = typeof y;
return x && y && tx === 'object' && tx === ty ? (
ok(x).length === ok(y).length &&
ok(x).every(key => deepEqual(x[key], y[key]))
) : (x === y);
}
export function defineNotNumerableProperties(obj: {[key: string]: any}, names: string[]) {
//const perf = performance.now();
const props = {writable: true, configurable: true};
const out: {[name: string]: typeof props} = {};
names.forEach(name => {
if(obj[name] === undefined) {
out[name] = props;
}
});
Object.defineProperties(obj, out);
//console.log('defineNotNumerableProperties time:', performance.now() - perf);
}
export function getObjectKeysAndSort(object: any, sort: 'asc' | 'desc' = 'asc') {
const ids = Object.keys(object).map(i => +i);
if(sort == 'asc') return ids.sort((a, b) => a - b);
else return ids.sort((a, b) => b - a);
}
export function safeReplaceObject(wasObject: any, newObject: any) {
for(var key in wasObject) {
if(!newObject.hasOwnProperty(key) && key.charAt(0) != '$') {
delete wasObject[key];
}
}
for(var key in newObject) {
//if (newObject.hasOwnProperty(key)) { // useless
wasObject[key] = newObject[key];
//}
}
}
/**
* Will be used for FILE_REFERENCE_EXPIRED
* @param key
* @param wasObject
* @param newObject
*/
export function safeReplaceArrayInObject<K>(key: K, wasObject: any, newObject: any) {
if('byteLength' in newObject[key]) { // Uint8Array
newObject[key] = [...newObject[key]];
}
if(wasObject && wasObject[key] != newObject[key]) {
wasObject[key].length = newObject[key].length;
(newObject[key] as any[]).forEach((v, i) => {
wasObject[key][i] = v;
});
/* wasObject[key].set(newObject[key]); */
newObject[key] = wasObject[key];
}
}
export function isObject(object: any) {
return typeof(object) === 'object' && object !== null;
}

77
src/helpers/string.ts

@ -0,0 +1,77 @@ @@ -0,0 +1,77 @@
/* export function stringMiddleOverflow(str: string, maxLength: number) {
return str.length > maxLength ? str.slice(0, maxLength / 2 | 0) + '...' + str.slice(-Math.round(maxLength / 2)) : str;
} */
export function limitSymbols(str: string, length: number, limitFrom = length + 10) {
if(str.length > limitFrom) {
str = str.slice(0, length).replace(/(\n|\s)+$/, '') + '...';
}
return str;
}
// credits to https://github.com/sindresorhus/escape-string-regexp/blob/master/index.js
export function escapeRegExp(str: string) {
return str
.replace(/[|\\{}()[\]^$+*?.]/g, '\\$&')
.replace(/-/g, '\\x2d');
}
export function encodeEntities(value: string) {
return value.replace(/&/g, '&amp;').replace(/[\uD800-\uDBFF][\uDC00-\uDFFF]/g, (value) => {
var hi = value.charCodeAt(0);
var low = value.charCodeAt(1);
return '&#' + (((hi - 0xD800) * 0x400) + (low - 0xDC00) + 0x10000) + ';';
}).replace(/([^\#-~| |!])/g, (value) => { // non-alphanumeric
return '&#' + value.charCodeAt(0) + ';';
}).replace(/</g, '&lt;').replace(/>/g, '&gt;');
}
export function splitStringByLength(str: string, maxLength: number) {
if(str.length < maxLength) return [str];
let length = 0, lastSliceStartIndex = 0, arrayIndex = 0;
const delimiter = ' ';//'\n';
const out: string[] = [];
const cut = (end?: number) => {
let part = str.slice(lastSliceStartIndex, end);
const _arrayIndex = arrayIndex++;
if(part.length > maxLength) {
let overflowPart = part.slice(maxLength);
const splitted = splitStringByLength(overflowPart, maxLength);
splitted.forEach(part => {
out[arrayIndex++] = part;
});
part = part.slice(0, maxLength);
}
lastSliceStartIndex = end;
length = 0;
out[_arrayIndex] = (out[_arrayIndex] || '') + part;
};
let lastIndex = 0;
do {
let index = str.indexOf(delimiter, lastIndex);
if(index === -1) {
if(lastIndex != (str.length - 1)) {
cut();
}
break;
}
index += delimiter.length;
const partLength = index - lastIndex;
if((length + partLength) > maxLength) {
cut(length);
}
lastIndex = index;
length += partLength;
} while(true);
return out;
}

5
src/lib/appManagers/apiUpdatesManager.ts

@ -3,7 +3,6 @@ import { logger, LogLevels } from '../logger'; @@ -3,7 +3,6 @@ import { logger, LogLevels } from '../logger';
import apiManager from '../mtproto/mtprotoworker';
import $rootScope from '../rootScope';
//import networkerFactory from '../mtproto/networkerFactory';
import { tsNow } from "../utils";
import appChatsManager from "./appChatsManager";
import appPeersManager from "./appPeersManager";
import appStateManager from './appStateManager';
@ -400,7 +399,7 @@ export class ApiUpdatesManager { @@ -400,7 +399,7 @@ export class ApiUpdatesManager {
if(update._ == 'updateChannelTooLong') {
if(!curState.lastPtsUpdateTime ||
curState.lastPtsUpdateTime < tsNow() - 10000) {
curState.lastPtsUpdateTime < Date.now() - 10000) {
// this.log.trace('channel too long, get diff', channelID, update)
this.getChannelDifference(channelID);
}
@ -461,7 +460,7 @@ export class ApiUpdatesManager { @@ -461,7 +460,7 @@ export class ApiUpdatesManager {
curState.pts = update.pts;
popPts = true;
curState.lastPtsUpdateTime = tsNow();
curState.lastPtsUpdateTime = Date.now();
} else if(update.pts_count) {
// this.log.warn('Duplicate update', update)
return false;

3
src/lib/appManagers/appChatsManager.ts

@ -1,8 +1,9 @@ @@ -1,8 +1,9 @@
import { numberWithCommas } from "../../helpers/number";
import { isObject, safeReplaceObject, copy } from "../../helpers/object";
import { ChatAdminRights, ChatBannedRights, ChatFull, ChatParticipants, InputChannel, InputChatPhoto, InputFile, InputPeer, Updates } from "../../layer";
import apiManager from '../mtproto/mtprotoworker';
import { RichTextProcessor } from "../richtextprocessor";
import $rootScope from "../rootScope";
import { copy, isObject, numberWithCommas, safeReplaceObject } from "../utils";
import apiUpdatesManager from "./apiUpdatesManager";
import appMessagesManager from "./appMessagesManager";
import appProfileManager from "./appProfileManager";

5
src/lib/appManagers/appDialogsManager.ts

@ -1,4 +1,3 @@ @@ -1,4 +1,3 @@
import { ResolutionUnitSpecifier } from "fast-png";
import AvatarElement from "../../components/avatar";
import DialogsContextMenu from "../../components/dialogsContextMenu";
import { horizontalMenu } from "../../components/horizontalMenu";
@ -8,13 +7,13 @@ import { ripple } from "../../components/ripple"; @@ -8,13 +7,13 @@ import { ripple } from "../../components/ripple";
import Scrollable, { ScrollableX } from "../../components/scrollable";
import appSidebarLeft from "../../components/sidebarLeft";
import { formatDateAccordingToToday } from "../../helpers/date";
import { escapeRegExp } from "../../helpers/string";
import { isTouchSupported } from "../../helpers/touchSupport";
import { isSafari } from "../../helpers/userAgent";
import { logger, LogLevels } from "../logger";
import { RichTextProcessor } from "../richtextprocessor";
import $rootScope from "../rootScope";
import { cancelEvent, escapeRegExp, findUpClassName, positionElementByIndex } from "../utils";
import appChatsManager from "./appChatsManager";
import { findUpClassName, positionElementByIndex } from "../../helpers/dom";
import appImManager, { AppImManager } from "./appImManager";
import appMessagesManager, { Dialog, MyDialogFilter as DialogFilter } from "./appMessagesManager";
import appPeersManager from './appPeersManager';

5
src/lib/appManagers/appDocsManager.ts

@ -1,10 +1,11 @@ @@ -1,10 +1,11 @@
import { getFileNameByLocation } from '../../helpers/fileName';
import { FileURLType, getFileNameByLocation, getFileURL } from '../../helpers/fileName';
import { safeReplaceArrayInObject, defineNotNumerableProperties } from '../../helpers/object';
import { Document, InputFileLocation, PhotoSize } from '../../layer';
import { isObject } from '../mtproto/bin_utils';
import { MOUNT_CLASS_TO } from '../mtproto/mtproto_config';
import referenceDatabase, { ReferenceContext } from '../mtproto/referenceDatabase';
import opusDecodeController from '../opusDecodeController';
import { RichTextProcessor } from '../richtextprocessor';
import { defineNotNumerableProperties, FileURLType, getFileURL, isObject, safeReplaceArrayInObject } from '../utils';
import appDownloadManager, { DownloadBlob } from './appDownloadManager';
import appPhotosManager from './appPhotosManager';

50
src/lib/appManagers/appImManager.ts

@ -29,6 +29,8 @@ import appSidebarRight, { AppSidebarRight, RIGHT_COLUMN_ACTIVE_CLASSNAME } from @@ -29,6 +29,8 @@ import appSidebarRight, { AppSidebarRight, RIGHT_COLUMN_ACTIVE_CLASSNAME } from
import StickyIntersector from '../../components/stickyIntersector';
import { wrapAlbum, wrapDocument, wrapPhoto, wrapPoll, wrapReply, wrapSticker, wrapVideo } from '../../components/wrappers';
import mediaSizes, { ScreenSize } from '../../helpers/mediaSizes';
import { numberWithCommas } from '../../helpers/number';
import { defineNotNumerableProperties, getObjectKeysAndSort } from '../../helpers/object';
import { isTouchSupported } from '../../helpers/touchSupport';
import { isAndroid, isApple, isSafari } from '../../helpers/userAgent';
import { InputNotifyPeer, InputPeerNotifySettings, NotifyPeer, Update } from '../../layer';
@ -38,7 +40,7 @@ import apiManager from '../mtproto/mtprotoworker'; @@ -38,7 +40,7 @@ import apiManager from '../mtproto/mtprotoworker';
import { MOUNT_CLASS_TO } from '../mtproto/mtproto_config';
import { RichTextProcessor } from "../richtextprocessor";
import $rootScope from '../rootScope';
import { cancelEvent, defineNotNumerableProperties, findUpClassName, findUpTag, getObjectKeysAndSort, numberWithCommas, placeCaretAtEnd, whichChild } from "../utils";
import { cancelEvent, findUpClassName, findUpTag, placeCaretAtEnd, whichChild } from "../../helpers/dom";
import apiUpdatesManager from './apiUpdatesManager';
import appChatsManager, { Channel, Chat } from "./appChatsManager";
import appDialogsManager from "./appDialogsManager";
@ -327,7 +329,7 @@ export class AppImManager { @@ -327,7 +329,7 @@ export class AppImManager {
appSidebarRight.sharedMediaTab.renderNewMessages(message.peerID, [mid]);
let bubble = this.bubbles[tempID];
const bubble = this.bubbles[tempID];
if(bubble) {
this.bubbles[mid] = bubble;
@ -335,8 +337,8 @@ export class AppImManager { @@ -335,8 +337,8 @@ export class AppImManager {
// set new mids to album items for mediaViewer
if(message.grouped_id) {
let items = bubble.querySelectorAll('.album-item');
let groupIDs = getObjectKeysAndSort(appMessagesManager.groupedMessagesStorage[message.grouped_id]);
const items = bubble.querySelectorAll('.album-item');
const groupIDs = getObjectKeysAndSort(appMessagesManager.groupedMessagesStorage[message.grouped_id]);
(Array.from(items) as HTMLElement[]).forEach((item, idx) => {
item.dataset.mid = '' + groupIDs[idx];
});
@ -412,15 +414,15 @@ export class AppImManager { @@ -412,15 +414,15 @@ export class AppImManager {
}); */
this.needUpdate.forEachReverse((obj, idx) => {
if(obj.replyMid == mid) {
let {mid, replyMid} = this.needUpdate.splice(idx, 1)[0];
const {mid, replyMid} = this.needUpdate.splice(idx, 1)[0];
//this.log('messages_downloaded', mid, replyMid, i, this.needUpdate, this.needUpdate.length, mids, this.bubbles[mid]);
let bubble = this.bubbles[mid];
const bubble = this.bubbles[mid];
if(!bubble) return;
let message = appMessagesManager.getMessage(mid);
const message = appMessagesManager.getMessage(mid);
let repliedMessage = appMessagesManager.getMessage(replyMid);
const repliedMessage = appMessagesManager.getMessage(replyMid);
if(repliedMessage.deleted) { // чтобы не пыталось бесконечно загрузить удалённое сообщение
delete message.reply_to_mid; // WARNING!
}
@ -439,7 +441,7 @@ export class AppImManager { @@ -439,7 +441,7 @@ export class AppImManager {
});
$rootScope.$on('apiUpdate', (e) => {
let update = e.detail;
const update = e.detail;
this.handleUpdate(update);
});
@ -489,8 +491,8 @@ export class AppImManager { @@ -489,8 +491,8 @@ export class AppImManager {
return;
}
for(let timestamp in this.dateMessages) {
let d = this.dateMessages[timestamp];
for(const timestamp in this.dateMessages) {
const d = this.dateMessages[timestamp];
if(d.div == bubble) {
new PopupDatePicker(new Date(+timestamp), this.onDatePick).show();
break;
@ -518,7 +520,7 @@ export class AppImManager { @@ -518,7 +520,7 @@ export class AppImManager {
return;
}
let contactDiv: HTMLElement = findUpClassName(target, 'contact');
const contactDiv: HTMLElement = findUpClassName(target, 'contact');
if(contactDiv) {
this.setPeer(+contactDiv.dataset.peerID);
return;
@ -707,10 +709,10 @@ export class AppImManager { @@ -707,10 +709,10 @@ export class AppImManager {
new ChatSearch();
});
let onKeyDown = (e: KeyboardEvent) => {
const onKeyDown = (e: KeyboardEvent) => {
if($rootScope.overlayIsActive) return;
let target = e.target as HTMLElement;
const target = e.target as HTMLElement;
//if(target.tagName == 'INPUT') return;
@ -743,7 +745,7 @@ export class AppImManager { @@ -743,7 +745,7 @@ export class AppImManager {
document.body.addEventListener('keydown', onKeyDown);
this.goDownBtn.addEventListener('click', () => {
let dialog = appMessagesManager.getDialogByPeerID(this.peerID)[0];
const dialog = appMessagesManager.getDialogByPeerID(this.peerID)[0];
if(dialog) {
this.setPeer(this.peerID/* , dialog.top_message */);
@ -761,8 +763,8 @@ export class AppImManager { @@ -761,8 +763,8 @@ export class AppImManager {
//apiUpdatesManager.attach();
this.stickyIntersector = new StickyIntersector(this.scrollable.container, (stuck, target) => {
for(let timestamp in this.dateMessages) {
let dateMessage = this.dateMessages[timestamp];
for(const timestamp in this.dateMessages) {
const dateMessage = this.dateMessages[timestamp];
if(dateMessage.container == target) {
dateMessage.div.classList.toggle('is-sticky', stuck);
break;
@ -775,12 +777,12 @@ export class AppImManager { @@ -775,12 +777,12 @@ export class AppImManager {
return;
}
let readed: number[] = [];
const readed: number[] = [];
entries.forEach(entry => {
if(entry.isIntersecting) {
let target = entry.target as HTMLElement;
let mid = +target.dataset.mid;
const target = entry.target as HTMLElement;
const mid = +target.dataset.mid;
readed.push(mid);
this.unreadedObserver.unobserve(target);
this.unreaded.findAndSplice(id => id == mid);
@ -788,11 +790,11 @@ export class AppImManager { @@ -788,11 +790,11 @@ export class AppImManager {
});
if(readed.length) {
let max = Math.max(...readed);
const max = Math.max(...readed);
let length = readed.length;
for(let i = this.unreaded.length - 1; i >= 0; --i) {
let mid = this.unreaded[i];
const mid = this.unreaded[i];
if(mid < max) {
length++;
this.unreaded.splice(i, 1);
@ -1106,9 +1108,9 @@ export class AppImManager { @@ -1106,9 +1108,9 @@ export class AppImManager {
}
public getMountedBubble(mid: number) {
let message = appMessagesManager.getMessage(mid);
const message = appMessagesManager.getMessage(mid);
let bubble = this.bubbles[mid];
const bubble = this.bubbles[mid];
if(!bubble && message.grouped_id) {
const a = this.getAlbumBubble(message.grouped_id);
if(a) return a;

5
src/lib/appManagers/appMessagesManager.ts

@ -1,6 +1,10 @@ @@ -1,6 +1,10 @@
import ProgressivePreloader from "../../components/preloader";
import { listMergeSorted } from "../../helpers/array";
import { CancellablePromise, deferredPromise } from "../../helpers/cancellablePromise";
import { tsNow } from "../../helpers/date";
import { copy, defineNotNumerableProperties, deepEqual, safeReplaceObject, getObjectKeysAndSort } from "../../helpers/object";
import { randomLong } from "../../helpers/random";
import { splitStringByLength, limitSymbols } from "../../helpers/string";
import { Dialog as MTDialog, DialogFilter, DialogPeer, DocumentAttribute, InputMessage, Message, MessageAction, MessagesDialogs, MessagesFilter, MessagesMessages, MessagesPeerDialogs, MethodDeclMap, PhotoSize, SendMessageAction, Update } from "../../layer";
import { InvokeApiOptions, Modify } from "../../types";
import { langPack } from "../langPack";
@ -14,7 +18,6 @@ import { RichTextProcessor } from "../richtextprocessor"; @@ -14,7 +18,6 @@ import { RichTextProcessor } from "../richtextprocessor";
import $rootScope from "../rootScope";
import searchIndexManager from '../searchIndexManager';
import AppStorage from '../storage';
import { copy, deepEqual, defineNotNumerableProperties, getObjectKeysAndSort, limitSymbols, listMergeSorted, safeReplaceObject, splitStringByLength, tsNow } from "../utils";
//import { telegramMeWebService } from "../mtproto/mtproto";
import apiUpdatesManager from "./apiUpdatesManager";
import appChatsManager from "./appChatsManager";

2
src/lib/appManagers/appPeersManager.ts

@ -1,7 +1,7 @@ @@ -1,7 +1,7 @@
import { isObject } from "../../helpers/object";
import { DialogPeer, InputDialogPeer, InputPeer, Peer } from "../../layer";
import { RichTextProcessor } from "../richtextprocessor";
import $rootScope from "../rootScope";
import { isObject } from "../utils";
import appChatsManager from "./appChatsManager";
import appUsersManager from "./appUsersManager";

3
src/lib/appManagers/appPhotosManager.ts

@ -1,12 +1,13 @@ @@ -1,12 +1,13 @@
import { bytesFromHex } from "../../helpers/bytes";
import { CancellablePromise } from "../../helpers/cancellablePromise";
import { getFileNameByLocation } from "../../helpers/fileName";
import { safeReplaceArrayInObject, defineNotNumerableProperties, isObject } from "../../helpers/object";
import { isSafari } from "../../helpers/userAgent";
import { FileLocation, InputFileLocation, Photo, PhotoSize, PhotosPhotos } from "../../layer";
import { DownloadOptions } from "../mtproto/apiFileManager";
import apiManager from "../mtproto/mtprotoworker";
import referenceDatabase, { ReferenceContext } from "../mtproto/referenceDatabase";
import { calcImageInBox, defineNotNumerableProperties, isObject, safeReplaceArrayInObject } from "../utils";
import { calcImageInBox } from "../../helpers/dom";
import { MyDocument } from "./appDocsManager";
import appDownloadManager from "./appDownloadManager";
import appUsersManager from "./appUsersManager";

2
src/lib/appManagers/appPollsManager.ts

@ -1,10 +1,10 @@ @@ -1,10 +1,10 @@
import { copy } from "../../helpers/object";
import { InputMedia } from "../../layer";
import { logger, LogLevels } from "../logger";
import apiManager from "../mtproto/mtprotoworker";
import { MOUNT_CLASS_TO } from "../mtproto/mtproto_config";
import { RichTextProcessor } from "../richtextprocessor";
import $rootScope from "../rootScope";
import { copy } from "../utils";
import apiUpdatesManager from "./apiUpdatesManager";
import appMessagesManager from './appMessagesManager';
import appPeersManager from './appPeersManager';

3
src/lib/appManagers/appUsersManager.ts

@ -1,4 +1,6 @@ @@ -1,4 +1,6 @@
import { formatPhoneNumber } from "../../components/misc";
import { tsNow } from "../../helpers/date";
import { safeReplaceObject, isObject } from "../../helpers/object";
import { InputUser, Update, User as MTUser, UserStatus } from "../../layer";
//import apiManager from '../mtproto/apiManager';
import apiManager from '../mtproto/mtprotoworker';
@ -6,7 +8,6 @@ import serverTimeManager from "../mtproto/serverTimeManager"; @@ -6,7 +8,6 @@ import serverTimeManager from "../mtproto/serverTimeManager";
import { RichTextProcessor } from "../richtextprocessor";
import $rootScope from "../rootScope";
import searchIndexManager from "../searchIndexManager";
import { isObject, safeReplaceObject, tsNow } from "../utils";
import appChatsManager from "./appChatsManager";
import appPeersManager from "./appPeersManager";
import appStateManager from "./appStateManager";

3
src/lib/appManagers/appWebPagesManager.ts

@ -1,9 +1,10 @@ @@ -1,9 +1,10 @@
import { limitSymbols, safeReplaceObject } from "../utils";
import appPhotosManager from "./appPhotosManager";
import appDocsManager from "./appDocsManager";
import { RichTextProcessor } from "../richtextprocessor";
import { ReferenceContext } from "../mtproto/referenceDatabase";
import $rootScope from "../rootScope";
import { safeReplaceObject } from "../../helpers/object";
import { limitSymbols } from "../../helpers/string";
class AppWebPagesManager {
webpages: any = {};

2
src/lib/mediaPlayer.ts

@ -1,4 +1,4 @@ @@ -1,4 +1,4 @@
import { cancelEvent } from "./utils";
import { cancelEvent } from "../helpers/dom";
import appMediaPlaybackController from "../components/appMediaPlaybackController";
import { isAppleMobile } from "../helpers/userAgent";
import { isTouchSupported } from "../helpers/touchSupport";

2
src/lib/mtproto/mtprotoworker.ts

@ -1,11 +1,11 @@ @@ -1,11 +1,11 @@
import MTProtoWorker from 'worker-loader!./mtproto.worker';
import { isObject } from '../../helpers/object';
import type { MethodDeclMap } from '../../layer';
import type { InvokeApiOptions } from '../../types';
import CryptoWorkerMethods from '../crypto/crypto_methods';
import { logger } from '../logger';
import $rootScope from '../rootScope';
import AppStorage from '../storage';
import { isObject } from '../utils';
import webpWorkerController from '../webp/webpWorkerController';
import type { DownloadOptions } from './apiFileManager';
import { ApiError } from './apiManager';

2
src/lib/mtproto/referenceDatabase.ts

@ -1,8 +1,8 @@ @@ -1,8 +1,8 @@
import appMessagesManager from "../appManagers/appMessagesManager";
import { Photo } from "../../layer";
import { deepEqual } from "../utils";
import { MOUNT_CLASS_TO } from "./mtproto_config";
import { bytesToHex } from "../../helpers/bytes";
import { deepEqual } from "../../helpers/object";
export type ReferenceContext = ReferenceContext.referenceContextProfilePhoto | ReferenceContext.referenceContextMessage;
export namespace ReferenceContext {

2
src/lib/mtproto/serverTimeManager.ts

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
import { tsNow } from '../../helpers/date';
import AppStorage from '../storage';
import { tsNow } from '../utils';
export class ServerTimeManager {
public timestampNow = tsNow(true);

3
src/lib/richtextprocessor.ts

@ -1,10 +1,11 @@ @@ -1,10 +1,11 @@
import {encodeEntities, copy} from './utils';
import Config from './config';
import emojiRegExp from '../emoji/regex';
import { encodeEmoji } from '../emoji';
import { MOUNT_CLASS_TO } from './mtproto/mtproto_config';
import { MessageEntity } from '../layer';
import { copy } from '../helpers/object';
import { encodeEntities } from '../helpers/string';
const EmojiHelper = {
emojiMap: (code: string) => { return code; },

569
src/lib/utils.ts

@ -1,569 +0,0 @@ @@ -1,569 +0,0 @@
import type { DownloadOptions } from "./mtproto/apiFileManager";
/* export function isInDOM(element: Element, parentNode?: HTMLElement): boolean {
if(!element) {
return false;
}
parentNode = parentNode || document.body;
if(element == parentNode) {
return true;
}
return isInDOM(element.parentNode as HTMLElement, parentNode);
} */
export function isInDOM(element: Element): boolean {
return element?.isConnected;
}
/* export function checkDragEvent(e: any) {
if(!e || e.target && (e.target.tagName == 'IMG' || e.target.tagName == 'A')) return false
if(e.dataTransfer && e.dataTransfer.types) {
for(var i = 0; i < e.dataTransfer.types.length; i++) {
if(e.dataTransfer.types[i] == 'Files') {
return true;
}
}
} else {
return true;
}
return false;
} */
export function cancelEvent (event: Event) {
event = event || window.event;
if(event) {
// @ts-ignore
event = event.originalEvent || event;
try {
if(event.stopPropagation) event.stopPropagation();
if(event.preventDefault) event.preventDefault();
event.returnValue = false;
event.cancelBubble = true;
} catch(err) {}
}
return false;
}
export function getRichValue(field: any) {
if(!field) {
return '';
}
var lines: string[] = [];
var line: string[] = [];
getRichElementValue(field, lines, line);
if (line.length) {
lines.push(line.join(''));
}
var value = lines.join('\n');
value = value.replace(/\u00A0/g, ' ');
return value;
}
export function placeCaretAtEnd(el: HTMLElement) {
el.focus();
if(typeof window.getSelection != "undefined" && typeof document.createRange != "undefined") {
var range = document.createRange();
range.selectNodeContents(el);
range.collapse(false);
var sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(range);
// @ts-ignore
} else if(typeof document.body.createTextRange != "undefined") {
// @ts-ignore
var textRange = document.body.createTextRange();
textRange.moveToElementText(el);
textRange.collapse(false);
textRange.select();
}
}
export function getRichElementValue(node: any, lines: string[], line: string[], selNode?: Node, selOffset?: number) {
if(node.nodeType == 3) { // TEXT
if(selNode === node) {
var value = node.nodeValue
line.push(value.substr(0, selOffset) + '\x01' + value.substr(selOffset))
} else {
line.push(node.nodeValue)
}
return
}
if (node.nodeType != 1) { // NON-ELEMENT
return
}
var isSelected = (selNode === node)
var isBlock = node.tagName == 'DIV' || node.tagName == 'P'
var curChild
if(isBlock && line.length || node.tagName == 'BR') {
lines.push(line.join(''))
line.splice(0, line.length)
} else if(node.tagName == 'IMG') {
if(node.alt) {
line.push(node.alt);
}
}
if(isSelected && !selOffset) {
line.push('\x01');
}
var curChild = node.firstChild;
while(curChild) {
getRichElementValue(curChild, lines, line, selNode, selOffset);
curChild = curChild.nextSibling;
}
if(isSelected && selOffset) {
line.push('\x01');
}
if(isBlock && line.length) {
lines.push(line.join(''));
line.splice(0, line.length);
}
}
/* if (Config.Modes.animations &&
typeof window.requestAnimationFrame == 'function') {
window.onAnimationFrameCallback = function (cb) {
return (function () {
window.requestAnimationFrame(cb)
})
}
} else {
window.onAnimationFrameCallback = function (cb) {
return cb
}
} */
// generate a path's arc data parameter
// http://www.w3.org/TR/SVG/paths.html#PathDataEllipticalArcCommands
var arcParameter = function(rx: number, ry: number, xAxisRotation: number, largeArcFlag: number, sweepFlag: number, x: number, y: number) {
return [rx, ',', ry, ' ',
xAxisRotation, ' ',
largeArcFlag, ',',
sweepFlag, ' ',
x, ',', y ].join('');
};
export function generatePathData(x: number, y: number, width: number, height: number, tl: number, tr: number, br: number, bl: number) {
var data = [];
// start point in top-middle of the rectangle
data.push('M' + (x + width / 2) + ',' + y);
// next we go to the right
data.push('H' + (x + width - tr));
if (tr > 0) {
// now we draw the arc in the top-right corner
data.push('A' + arcParameter(tr, tr, 0, 0, 1, (x + width), (y + tr)));
}
// next we go down
data.push('V' + (y + height - br));
if (br > 0) {
// now we draw the arc in the lower-right corner
data.push('A' + arcParameter(br, br, 0, 0, 1, (x + width - br), (y + height)));
}
// now we go to the left
data.push('H' + (x + bl));
if (bl > 0) {
// now we draw the arc in the lower-left corner
data.push('A' + arcParameter(bl, bl, 0, 0, 1, (x + 0), (y + height - bl)));
}
// next we go up
data.push('V' + (y + tl));
if (tl > 0) {
// now we draw the arc in the top-left corner
data.push('A' + arcParameter(tl, tl, 0, 0, 1, (x + tl), (y + 0)));
}
// and we close the path
data.push('Z');
return data.join(' ');
};
export function isObject(object: any) {
return typeof(object) === 'object' && object !== null;
}
export function tsNow(seconds?: boolean) {
var t = +new Date();
return seconds ? Math.floor(t / 1000) : t;
}
export function safeReplaceObject(wasObject: any, newObject: any) {
for(var key in wasObject) {
if(!newObject.hasOwnProperty(key) && key.charAt(0) != '$') {
delete wasObject[key];
}
}
for(var key in newObject) {
//if (newObject.hasOwnProperty(key)) { // useless
wasObject[key] = newObject[key];
//}
}
}
/**
* Will be used for FILE_REFERENCE_EXPIRED
* @param key
* @param wasObject
* @param newObject
*/
export function safeReplaceArrayInObject<K>(key: K, wasObject: any, newObject: any) {
if('byteLength' in newObject[key]) { // Uint8Array
newObject[key] = [...newObject[key]];
}
if(wasObject && wasObject[key] != newObject[key]) {
wasObject[key].length = newObject[key].length;
(newObject[key] as any[]).forEach((v, i) => {
wasObject[key][i] = v;
});
/* wasObject[key].set(newObject[key]); */
newObject[key] = wasObject[key];
}
}
export function limitSymbols(str: string, length: number, limitFrom = length + 10) {
if(str.length > limitFrom) {
str = str.slice(0, length).replace(/(\n|\s)+$/, '') + '...';
}
return str;
}
export function numberWithCommas(x: number) {
var parts = x.toString().split(".");
parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ",");
return parts.join(".");
}
//export function findUpClassName<T>(el: any, className: string): T;
export function findUpClassName(el: any, className: string): HTMLElement {
if(el.classList.contains(className)) return el; // 03.02.2020
while(el.parentElement) {
el = el.parentElement;
if(el.classList.contains(className))
return el;
}
return null;
}
export function findUpTag(el: any, tag: string): HTMLElement {
if(el.tagName == tag) return el; // 03.02.2020
while(el.parentElement) {
el = el.parentElement;
if(el.tagName === tag)
return el;
}
return null;
}
export function findUpAttribute(el: any, attribute: string): HTMLElement {
if(el.getAttribute(attribute) != null) return el; // 03.02.2020
while(el.parentElement) {
el = el.parentElement;
if(el.getAttribute(attribute) != null)
return el;
}
return null;
}
export function getObjectKeysAndSort(object: any, sort: 'asc' | 'desc' = 'asc') {
const ids = Object.keys(object).map(i => +i);
if(sort == 'asc') return ids.sort((a, b) => a - b);
else return ids.sort((a, b) => b - a);
}
export function whichChild(elem: Node) {
if(!elem.parentNode) {
return -1;
}
let i = 0;
// @ts-ignore
while((elem = elem.previousElementSibling) != null) ++i;
return i;
};
export function copy<T>(obj: T): T {
//in case of premitives
if(obj === null || typeof(obj) !== "object") {
return obj;
}
//date objects should be
if(obj instanceof Date) {
return new Date(obj.getTime()) as any;
}
//handle Array
if(Array.isArray(obj)){
var clonedArr: any = [];
obj.forEach(function(element){
clonedArr.push(copy(element))
});
return clonedArr;
}
//lastly, handle objects
// @ts-ignore
let clonedObj = new obj.constructor();
for(var prop in obj){
if(obj.hasOwnProperty(prop)){
clonedObj[prop] = copy(obj[prop]);
}
}
return clonedObj;
}
export function formatBytes(bytes: number, decimals = 2) {
if (bytes === 0) return '0 Bytes';
const k = 1024;
const dm = decimals < 0 ? 0 : decimals;
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i];
}
export function formatNumber(bytes: number, decimals = 2) {
if(bytes === 0) return '0';
const k = 1000;
const dm = decimals < 0 ? 0 : decimals;
const sizes = ['', 'K', 'M', 'B', 'T'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + sizes[i];
}
export function deepEqual(x: any, y: any): boolean {
const ok = Object.keys, tx = typeof x, ty = typeof y;
return x && y && tx === 'object' && tx === ty ? (
ok(x).length === ok(y).length &&
ok(x).every(key => deepEqual(x[key], y[key]))
) : (x === y);
}
export function listMergeSorted(list1: any, list2: any) {
list1 = list1 || [];
list2 = list2 || [];
var result = copy(list1);
var minID = list1.length ? list1[list1.length - 1] : 0xFFFFFFFF;
for (var i = 0; i < list2.length; i++) {
if (list2[i] < minID) {
result.push(list2[i]);
}
}
return result;
}
// credits to https://github.com/sindresorhus/escape-string-regexp/blob/master/index.js
export function escapeRegExp(str: string) {
return str
.replace(/[|\\{}()[\]^$+*?.]/g, '\\$&')
.replace(/-/g, '\\x2d');
}
export function encodeEntities(value: string) {
return value.replace(/&/g, '&amp;').replace(/[\uD800-\uDBFF][\uDC00-\uDFFF]/g, (value) => {
var hi = value.charCodeAt(0);
var low = value.charCodeAt(1);
return '&#' + (((hi - 0xD800) * 0x400) + (low - 0xDC00) + 0x10000) + ';';
}).replace(/([^\#-~| |!])/g, (value) => { // non-alphanumeric
return '&#' + value.charCodeAt(0) + ';';
}).replace(/</g, '&lt;').replace(/>/g, '&gt;');
}
export function fillPropertyValue(str: string) {
let splitted = str.split(' ');
if(splitted.length != 4) {
if(!splitted[0]) splitted[0] = '0px';
for(let i = splitted.length; i < 4; ++i) {
splitted[i] = splitted[i % 2] || splitted[0] || '0px';
}
}
return splitted;
}
export function calcImageInBox (imageW: number, imageH: number, boxW: number, boxH: number, noZoom?: boolean) {
if(imageW < boxW && imageH < boxH) {
return {w: imageW, h: imageH};
}
var boxedImageW = boxW;
var boxedImageH = boxH;
if((imageW / imageH) > (boxW / boxH)) {
boxedImageH = (imageH * boxW / imageW) | 0;
} else {
boxedImageW = (imageW * boxH / imageH) | 0;
if(boxedImageW > boxW) {
boxedImageH = (boxedImageH * boxW / boxedImageW) | 0;
boxedImageW = boxW;
}
}
// if (Config.Navigator.retina) {
// imageW = Math.floor(imageW / 2)
// imageH = Math.floor(imageH / 2)
// }
if(noZoom && boxedImageW >= imageW && boxedImageH >= imageH) {
boxedImageW = imageW;
boxedImageH = imageH;
}
return {w: boxedImageW, h: boxedImageH};
}
export function getEmojiToneIndex(input: string) {
let match = input.match(/[\uDFFB-\uDFFF]/);
return match ? 5 - (57343 - match[0].charCodeAt(0)) : 0;
}
export type FileURLType = 'photo' | 'thumb' | 'document' | 'stream' | 'download';
export function getFileURL(type: FileURLType, options: DownloadOptions) {
//console.log('getFileURL', location);
//const perf = performance.now();
const encoded = encodeURIComponent(JSON.stringify(options));
//console.log('getFileURL encode:', performance.now() - perf, encoded);
return '/' + type + '/' + encoded;
}
export function positionElementByIndex(element: HTMLElement, container: HTMLElement, pos: number) {
const prevPos = whichChild(element);
if(prevPos == pos) {
return false;
} else if(prevPos != -1 && prevPos < pos) { // was higher
pos += 1;
}
if(container.childElementCount > pos) {
container.insertBefore(element, container.children[pos]);
} else {
container.append(element);
}
return true;
}
export function splitStringByLength(str: string, maxLength: number) {
if(str.length < maxLength) return [str];
let length = 0, lastSliceStartIndex = 0, arrayIndex = 0;
const delimiter = ' ';//'\n';
const out: string[] = [];
const cut = (end?: number) => {
let part = str.slice(lastSliceStartIndex, end);
const _arrayIndex = arrayIndex++;
if(part.length > maxLength) {
let overflowPart = part.slice(maxLength);
const splitted = splitStringByLength(overflowPart, maxLength);
splitted.forEach(part => {
out[arrayIndex++] = part;
});
part = part.slice(0, maxLength);
}
lastSliceStartIndex = end;
length = 0;
out[_arrayIndex] = (out[_arrayIndex] || '') + part;
};
let lastIndex = 0;
do {
let index = str.indexOf(delimiter, lastIndex);
if(index === -1) {
if(lastIndex != (str.length - 1)) {
cut();
}
break;
}
index += delimiter.length;
const partLength = index - lastIndex;
if((length + partLength) > maxLength) {
cut(length);
}
lastIndex = index;
length += partLength;
} while(true);
return out;
}
export function defineNotNumerableProperties(obj: {[key: string]: any}, names: string[]) {
//const perf = performance.now();
const props = {writable: true, configurable: true};
const out: {[name: string]: typeof props} = {};
names.forEach(name => {
if(obj[name] === undefined) {
out[name] = props;
}
});
Object.defineProperties(obj, out);
//console.log('defineNotNumerableProperties time:', performance.now() - perf);
}
export function cancelSelection() {
if(window.getSelection) {
if(window.getSelection().empty) { // Chrome
window.getSelection().empty();
} else if(window.getSelection().removeAllRanges) { // Firefox
window.getSelection().removeAllRanges();
}
// @ts-ignore
} else if(document.selection) { // IE?
// @ts-ignore
document.selection.empty();
}
}
//(window as any).splitStringByLength = splitStringByLength;
export function getSelectedText(): string {
if(window.getSelection) {
return window.getSelection().toString();
// @ts-ignore
} else if(document.selection) {
// @ts-ignore
return document.selection.createRange().text;
}
return '';
}

2
src/pages/pagePassword.ts

@ -9,7 +9,7 @@ import LottieLoader, { RLottiePlayer } from '../lib/lottieLoader'; @@ -9,7 +9,7 @@ import LottieLoader, { RLottiePlayer } from '../lib/lottieLoader';
//import passwordManager from '../lib/mtproto/passwordManager';
import apiManager from '../lib/mtproto/mtprotoworker';
import passwordManager from '../lib/mtproto/passwordManager';
import { cancelEvent } from '../lib/utils';
import { cancelEvent } from '../helpers/dom';
import Page from './page';
import pageIm from './pageIm';

2
src/pages/pageSignIn.ts

@ -5,7 +5,7 @@ import appStateManager from "../lib/appManagers/appStateManager"; @@ -5,7 +5,7 @@ import appStateManager from "../lib/appManagers/appStateManager";
import apiManager from "../lib/mtproto/mtprotoworker";
import { App, Modes } from "../lib/mtproto/mtproto_config";
import { RichTextProcessor } from '../lib/richtextprocessor';
import { findUpTag } from "../lib/utils";
import { findUpTag } from "../helpers/dom";
import Page from "./page";
import pageAuthCode from "./pageAuthCode";
import pageSignQR from './pageSignQR';

2
src/pages/pagesManager.ts

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
import Page from "./page";
import { whichChild } from "../lib/utils";
import { whichChild } from "../helpers/dom";
import lottieLoader from "../lib/lottieLoader";
import { horizontalMenu } from "../components/horizontalMenu";
import { MOUNT_CLASS_TO } from "../lib/mtproto/mtproto_config";

2
src/scss/partials/_chatlist.scss

@ -116,7 +116,6 @@ @@ -116,7 +116,6 @@
color: #a3a3a3;
font-size: 1.125rem;
margin-left: .125rem;
display: inline;
animation: fade-in-opacity .2s ease forwards;
}
}
@ -274,7 +273,6 @@ @@ -274,7 +273,6 @@
flex: 0 0 auto;
content: " ";
background: url(assets/img/icon-verified.svg);
display: inline-block;
width: 20px;
height: 20px;
margin-left: .125rem;

6
src/scss/style.scss

@ -1017,3 +1017,9 @@ img.emoji { @@ -1017,3 +1017,9 @@ img.emoji {
// -webkit-user-select: none; /* disable selection/Copy of UIWebView */
// -webkit-touch-callout: none; /* disable the IOS popup when long-press on a link */
// }
middle-ellipsis-element {
width: 100%;
overflow: hidden;
display: block;
}

Loading…
Cancel
Save