diff --git a/src/components/animationIntersector.ts b/src/components/animationIntersector.ts
index f50b27e3..982dde83 100644
--- a/src/components/animationIntersector.ts
+++ b/src/components/animationIntersector.ts
@@ -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";
diff --git a/src/components/appMediaViewer.ts b/src/components/appMediaViewer.ts
index 4bb09467..d44ba09f 100644
--- a/src/components/appMediaViewer.ts
+++ b/src/components/appMediaViewer.ts
@@ -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";
diff --git a/src/components/appSearch.ts b/src/components/appSearch.ts
index da8e0ddd..a1c8b4b5 100644
--- a/src/components/appSearch.ts
+++ b/src/components/appSearch.ts
@@ -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;
diff --git a/src/components/appSelectPeers.ts b/src/components/appSelectPeers.ts
index 151bbd67..fa032218 100644
--- a/src/components/appSelectPeers.ts
+++ b/src/components/appSelectPeers.ts
@@ -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';
diff --git a/src/components/audio.ts b/src/components/audio.ts
index 05628922..ddb5cd45 100644
--- a/src/components/audio.ts
+++ b/src/components/audio.ts
@@ -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) {
const html = `
-
${title}
+
${title}
${subtitle}
`;
diff --git a/src/components/avatar.ts b/src/components/avatar.ts
index 332b7ea3..610063f6 100644
--- a/src/components/avatar.ts
+++ b/src/components/avatar.ts
@@ -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) => {
diff --git a/src/components/bubbleGroups.ts b/src/components/bubbleGroups.ts
index e5c98d72..f7a6742f 100644
--- a/src/components/bubbleGroups.ts
+++ b/src/components/bubbleGroups.ts
@@ -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
diff --git a/src/components/chat/audio.ts b/src/components/chat/audio.ts
index a27f6084..162f6bae 100644
--- a/src/components/chat/audio.ts
+++ b/src/components/chat/audio.ts
@@ -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";
diff --git a/src/components/chat/contextMenu.ts b/src/components/chat/contextMenu.ts
index 0d8bf795..f59f3b3e 100644
--- a/src/components/chat/contextMenu.ts
+++ b/src/components/chat/contextMenu.ts
@@ -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";
diff --git a/src/components/chat/input.ts b/src/components/chat/input.ts
index baafa896..7d722bee 100644
--- a/src/components/chat/input.ts
+++ b/src/components/chat/input.ts
@@ -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";
diff --git a/src/components/chat/messageRender.ts b/src/components/chat/messageRender.ts
index ce2e1235..89ae47be 100644
--- a/src/components/chat/messageRender.ts
+++ b/src/components/chat/messageRender.ts
@@ -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;
diff --git a/src/components/chat/pinnedContainer.ts b/src/components/chat/pinnedContainer.ts
index b0409711..47715b4f 100644
--- a/src/components/chat/pinnedContainer.ts
+++ b/src/components/chat/pinnedContainer.ts
@@ -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";
diff --git a/src/components/chat/search.ts b/src/components/chat/search.ts
index 8411d7e7..23e421c6 100644
--- a/src/components/chat/search.ts
+++ b/src/components/chat/search.ts
@@ -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";
diff --git a/src/components/chat/selection.ts b/src/components/chat/selection.ts
index b85463c1..7cf39957 100644
--- a/src/components/chat/selection.ts
+++ b/src/components/chat/selection.ts
@@ -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";
diff --git a/src/components/dialogsContextMenu.ts b/src/components/dialogsContextMenu.ts
index f6569b88..6dd05170 100644
--- a/src/components/dialogsContextMenu.ts
+++ b/src/components/dialogsContextMenu.ts
@@ -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";
diff --git a/src/components/emoticonsDropdown/index.ts b/src/components/emoticonsDropdown/index.ts
index 5f370a35..3abcf635 100644
--- a/src/components/emoticonsDropdown/index.ts
+++ b/src/components/emoticonsDropdown/index.ts
@@ -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";
diff --git a/src/components/gifsMasonry.ts b/src/components/gifsMasonry.ts
index 2524fdf4..c8b5d9ec 100644
--- a/src/components/gifsMasonry.ts
+++ b/src/components/gifsMasonry.ts
@@ -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";
diff --git a/src/components/groupedLayout.ts b/src/components/groupedLayout.ts
index 192be05d..29988835 100644
--- a/src/components/groupedLayout.ts
+++ b/src/components/groupedLayout.ts
@@ -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 = {
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(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 {
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.);
});
}
diff --git a/src/components/horizontalMenu.ts b/src/components/horizontalMenu.ts
index e9845c1c..983dd018 100644
--- a/src/components/horizontalMenu.ts
+++ b/src/components/horizontalMenu.ts
@@ -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) {
diff --git a/src/components/middleEllipsis.ts b/src/components/middleEllipsis.ts
new file mode 100644
index 00000000..2a6afc9c
--- /dev/null
+++ b/src/components/middleEllipsis.ts
@@ -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
+ * A Javascript solution to middle ellipsis
+ * A Javascript solution to middle ellipsis
+ * A Javascript solution to middle ellipsis
+ */
+const attributeName = 'data-middle-ellipsis';
+const ellipsis = '…';
+const map: Map = new Map();
+
+const testQueue: Set = 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);
diff --git a/src/components/poll.ts b/src/components/poll.ts
index 8ca788cd..023daf80 100644
--- a/src/components/poll.ts
+++ b/src/components/poll.ts
@@ -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";
diff --git a/src/components/popup.ts b/src/components/popup.ts
index 1ccd6cc0..683ae366 100644
--- a/src/components/popup.ts
+++ b/src/components/popup.ts
@@ -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 {
diff --git a/src/components/popupCreatePoll.ts b/src/components/popupCreatePoll.ts
index 7176b730..0afefbcc 100644
--- a/src/components/popupCreatePoll.ts
+++ b/src/components/popupCreatePoll.ts
@@ -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";
diff --git a/src/components/popupNewMedia.ts b/src/components/popupNewMedia.ts
index 3185607a..4282a782 100644
--- a/src/components/popupNewMedia.ts
+++ b/src/components/popupNewMedia.ts
@@ -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";
diff --git a/src/components/popupStickers.ts b/src/components/popupStickers.ts
index 4de24c4f..ba98c529 100644
--- a/src/components/popupStickers.ts
+++ b/src/components/popupStickers.ts
@@ -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";
diff --git a/src/components/preloader.ts b/src/components/preloader.ts
index 6bc8e3b8..c2e3a26f 100644
--- a/src/components/preloader.ts
+++ b/src/components/preloader.ts
@@ -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 {
diff --git a/src/components/ripple.ts b/src/components/ripple.ts
index 58e9cc8a..72529451 100644
--- a/src/components/ripple.ts
+++ b/src/components/ripple.ts
@@ -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 = () => Promise.resolve(), onEnd: (id: number) => void = null) {
diff --git a/src/components/sidebarLeft/index.ts b/src/components/sidebarLeft/index.ts
index 731e2bbc..5ab26055 100644
--- a/src/components/sidebarLeft/index.ts
+++ b/src/components/sidebarLeft/index.ts
@@ -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";
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";
diff --git a/src/components/sidebarLeft/tabs/chatFolders.ts b/src/components/sidebarLeft/tabs/chatFolders.ts
index 905b32e5..d50ce129 100644
--- a/src/components/sidebarLeft/tabs/chatFolders.ts
+++ b/src/components/sidebarLeft/tabs/chatFolders.ts
@@ -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";
diff --git a/src/components/sidebarLeft/tabs/editFolder.ts b/src/components/sidebarLeft/tabs/editFolder.ts
index 6a5135c7..15dc7212 100644
--- a/src/components/sidebarLeft/tabs/editFolder.ts
+++ b/src/components/sidebarLeft/tabs/editFolder.ts
@@ -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";
diff --git a/src/components/sidebarLeft/tabs/includedChats.ts b/src/components/sidebarLeft/tabs/includedChats.ts
index 4ad92098..928ab643 100644
--- a/src/components/sidebarLeft/tabs/includedChats.ts
+++ b/src/components/sidebarLeft/tabs/includedChats.ts
@@ -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;
diff --git a/src/components/sidebarRight/tabs/forward.ts b/src/components/sidebarRight/tabs/forward.ts
index 2bbbe58a..e67413a6 100644
--- a/src/components/sidebarRight/tabs/forward.ts
+++ b/src/components/sidebarRight/tabs/forward.ts
@@ -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";
diff --git a/src/components/sidebarRight/tabs/gifs.ts b/src/components/sidebarRight/tabs/gifs.ts
index 0e98cead..194c8830 100644
--- a/src/components/sidebarRight/tabs/gifs.ts
+++ b/src/components/sidebarRight/tabs/gifs.ts
@@ -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";
diff --git a/src/components/sidebarRight/tabs/sharedMedia.ts b/src/components/sidebarRight/tabs/sharedMedia.ts
index 49e4645a..f84ea172 100644
--- a/src/components/sidebarRight/tabs/sharedMedia.ts
+++ b/src/components/sidebarRight/tabs/sharedMedia.ts
@@ -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";
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 {
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 {
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;
diff --git a/src/components/sidebarRight/tabs/stickers.ts b/src/components/sidebarRight/tabs/stickers.ts
index 0fd3b184..e3393dce 100644
--- a/src/components/sidebarRight/tabs/stickers.ts
+++ b/src/components/sidebarRight/tabs/stickers.ts
@@ -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";
diff --git a/src/components/wrappers.ts b/src/components/wrappers.ts
index b4b9c1a3..81da62d1 100644
--- a/src/components/wrappers.ts
+++ b/src/components/wrappers.ts
@@ -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';
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';
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
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
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
docDiv.innerHTML = `
${!uploading ? `` : ''}
- ${fileName}
+ ${fileName}
${size}
`;
diff --git a/src/emoji/index.ts b/src/emoji/index.ts
index f480ce3c..5844a502 100644
--- a/src/emoji/index.ts
+++ b/src/emoji/index.ts
@@ -33,4 +33,9 @@ export function toCodePoints(unicodeSurrogates: string): Array {
}
return points;
+}
+
+export function getEmojiToneIndex(input: string) {
+ let match = input.match(/[\uDFFB-\uDFFF]/);
+ return match ? 5 - (57343 - match[0].charCodeAt(0)) : 0;
}
\ No newline at end of file
diff --git a/src/helpers/array.ts b/src/helpers/array.ts
new file mode 100644
index 00000000..def55103
--- /dev/null
+++ b/src/helpers/array.ts
@@ -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);
\ No newline at end of file
diff --git a/src/helpers/date.ts b/src/helpers/date.ts
index 2b78b9f9..379d3369 100644
--- a/src/helpers/date.ts
+++ b/src/helpers/date.ts
@@ -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);
-};
\ No newline at end of file
+};
+
+export function tsNow(seconds?: true) {
+ const t = Date.now();
+ return seconds ? Math.floor(t / 1000) : t;
+}
\ No newline at end of file
diff --git a/src/helpers/dom.ts b/src/helpers/dom.ts
new file mode 100644
index 00000000..de86a929
--- /dev/null
+++ b/src/helpers/dom.ts
@@ -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(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 '';
+}
diff --git a/src/helpers/fileName.ts b/src/helpers/fileName.ts
index 3c1851c7..db29e57e 100644
--- a/src/helpers/fileName.ts
+++ b/src/helpers/fileName.ts
@@ -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
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;
}
\ No newline at end of file
diff --git a/src/helpers/number.ts b/src/helpers/number.ts
new file mode 100644
index 00000000..a6419e19
--- /dev/null
+++ b/src/helpers/number.ts
@@ -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);
+}
+
diff --git a/src/helpers/object.ts b/src/helpers/object.ts
new file mode 100644
index 00000000..87ec0c19
--- /dev/null
+++ b/src/helpers/object.ts
@@ -0,0 +1,94 @@
+export function copy(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(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;
+}
\ No newline at end of file
diff --git a/src/helpers/string.ts b/src/helpers/string.ts
new file mode 100644
index 00000000..0a3dc926
--- /dev/null
+++ b/src/helpers/string.ts
@@ -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, '&').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, '>');
+}
+
+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;
+}
\ No newline at end of file
diff --git a/src/lib/appManagers/apiUpdatesManager.ts b/src/lib/appManagers/apiUpdatesManager.ts
index 4b9458a1..5604812d 100644
--- a/src/lib/appManagers/apiUpdatesManager.ts
+++ b/src/lib/appManagers/apiUpdatesManager.ts
@@ -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 {
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 {
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;
diff --git a/src/lib/appManagers/appChatsManager.ts b/src/lib/appManagers/appChatsManager.ts
index 56fea42d..1e02c19d 100644
--- a/src/lib/appManagers/appChatsManager.ts
+++ b/src/lib/appManagers/appChatsManager.ts
@@ -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";
diff --git a/src/lib/appManagers/appDialogsManager.ts b/src/lib/appManagers/appDialogsManager.ts
index 6e1a3a4b..373d1617 100644
--- a/src/lib/appManagers/appDialogsManager.ts
+++ b/src/lib/appManagers/appDialogsManager.ts
@@ -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";
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';
diff --git a/src/lib/appManagers/appDocsManager.ts b/src/lib/appManagers/appDocsManager.ts
index f854c321..9dcf5896 100644
--- a/src/lib/appManagers/appDocsManager.ts
+++ b/src/lib/appManagers/appDocsManager.ts
@@ -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';
diff --git a/src/lib/appManagers/appImManager.ts b/src/lib/appManagers/appImManager.ts
index cf7aef0c..30e61ca7 100644
--- a/src/lib/appManagers/appImManager.ts
+++ b/src/lib/appManagers/appImManager.ts
@@ -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';
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 {
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 {
// 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 {
}); */
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 {
});
$rootScope.$on('apiUpdate', (e) => {
- let update = e.detail;
+ const update = e.detail;
this.handleUpdate(update);
});
@@ -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 {
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 {
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 {
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 {
//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 {
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 {
});
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 {
}
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;
diff --git a/src/lib/appManagers/appMessagesManager.ts b/src/lib/appManagers/appMessagesManager.ts
index b74131cd..9f078c37 100644
--- a/src/lib/appManagers/appMessagesManager.ts
+++ b/src/lib/appManagers/appMessagesManager.ts
@@ -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";
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";
diff --git a/src/lib/appManagers/appPeersManager.ts b/src/lib/appManagers/appPeersManager.ts
index 7b9352d3..947af072 100644
--- a/src/lib/appManagers/appPeersManager.ts
+++ b/src/lib/appManagers/appPeersManager.ts
@@ -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";
diff --git a/src/lib/appManagers/appPhotosManager.ts b/src/lib/appManagers/appPhotosManager.ts
index f63c1c72..7c07d63b 100644
--- a/src/lib/appManagers/appPhotosManager.ts
+++ b/src/lib/appManagers/appPhotosManager.ts
@@ -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";
diff --git a/src/lib/appManagers/appPollsManager.ts b/src/lib/appManagers/appPollsManager.ts
index eab54926..73dca2c7 100644
--- a/src/lib/appManagers/appPollsManager.ts
+++ b/src/lib/appManagers/appPollsManager.ts
@@ -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';
diff --git a/src/lib/appManagers/appUsersManager.ts b/src/lib/appManagers/appUsersManager.ts
index 4c04eec0..c5aa46da 100644
--- a/src/lib/appManagers/appUsersManager.ts
+++ b/src/lib/appManagers/appUsersManager.ts
@@ -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";
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";
diff --git a/src/lib/appManagers/appWebPagesManager.ts b/src/lib/appManagers/appWebPagesManager.ts
index 3a30ad82..0a12f375 100644
--- a/src/lib/appManagers/appWebPagesManager.ts
+++ b/src/lib/appManagers/appWebPagesManager.ts
@@ -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 = {};
diff --git a/src/lib/mediaPlayer.ts b/src/lib/mediaPlayer.ts
index 007e9698..361cce12 100644
--- a/src/lib/mediaPlayer.ts
+++ b/src/lib/mediaPlayer.ts
@@ -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";
diff --git a/src/lib/mtproto/mtprotoworker.ts b/src/lib/mtproto/mtprotoworker.ts
index 7eb2fefc..7a412907 100644
--- a/src/lib/mtproto/mtprotoworker.ts
+++ b/src/lib/mtproto/mtprotoworker.ts
@@ -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';
diff --git a/src/lib/mtproto/referenceDatabase.ts b/src/lib/mtproto/referenceDatabase.ts
index 60c9bf13..dfd5d8eb 100644
--- a/src/lib/mtproto/referenceDatabase.ts
+++ b/src/lib/mtproto/referenceDatabase.ts
@@ -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 {
diff --git a/src/lib/mtproto/serverTimeManager.ts b/src/lib/mtproto/serverTimeManager.ts
index 5f4dcd67..37dec425 100644
--- a/src/lib/mtproto/serverTimeManager.ts
+++ b/src/lib/mtproto/serverTimeManager.ts
@@ -1,5 +1,5 @@
+import { tsNow } from '../../helpers/date';
import AppStorage from '../storage';
-import { tsNow } from '../utils';
export class ServerTimeManager {
public timestampNow = tsNow(true);
diff --git a/src/lib/richtextprocessor.ts b/src/lib/richtextprocessor.ts
index 1fb38f0a..e3dcbd2d 100644
--- a/src/lib/richtextprocessor.ts
+++ b/src/lib/richtextprocessor.ts
@@ -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; },
diff --git a/src/lib/utils.ts b/src/lib/utils.ts
deleted file mode 100644
index 6de19153..00000000
--- a/src/lib/utils.ts
+++ /dev/null
@@ -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(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(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(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, '&').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, '>');
-}
-
-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 '';
-}
\ No newline at end of file
diff --git a/src/pages/pagePassword.ts b/src/pages/pagePassword.ts
index 4ba9d742..74233a94 100644
--- a/src/pages/pagePassword.ts
+++ b/src/pages/pagePassword.ts
@@ -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';
diff --git a/src/pages/pageSignIn.ts b/src/pages/pageSignIn.ts
index b0a809fe..d83133ee 100644
--- a/src/pages/pageSignIn.ts
+++ b/src/pages/pageSignIn.ts
@@ -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';
diff --git a/src/pages/pagesManager.ts b/src/pages/pagesManager.ts
index 91843e82..232c4c6e 100644
--- a/src/pages/pagesManager.ts
+++ b/src/pages/pagesManager.ts
@@ -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";
diff --git a/src/scss/partials/_chatlist.scss b/src/scss/partials/_chatlist.scss
index 2a40b52c..38c5233b 100644
--- a/src/scss/partials/_chatlist.scss
+++ b/src/scss/partials/_chatlist.scss
@@ -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 @@
flex: 0 0 auto;
content: " ";
background: url(assets/img/icon-verified.svg);
- display: inline-block;
width: 20px;
height: 20px;
margin-left: .125rem;
diff --git a/src/scss/style.scss b/src/scss/style.scss
index 869b745d..efffe251 100644
--- a/src/scss/style.scss
+++ b/src/scss/style.scss
@@ -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;
+}