[Dev] Fix eslint; Update dependencies (#1754)

This commit is contained in:
Alexander Zinchuk 2022-03-19 21:18:43 +01:00
parent 0d4ab658fc
commit 07ffad4425
61 changed files with 2032 additions and 1929 deletions

View File

@ -2,6 +2,7 @@
"extends": [
"react-app",
"plugin:teactn/recommended",
"airbnb",
"airbnb-typescript"
],
"plugins": [
@ -34,11 +35,31 @@
"no-else-return": "off",
"no-plusplus": "off",
"no-void": "off",
"no-continue": "off",
"default-case": "off",
"no-param-reassign": "off",
"no-prototype-builtins": "off",
"no-await-in-loop": "off",
"no-nested-ternary": "off",
"function-paren-newline": ["error", "consistent"],
"prefer-destructuring": "off",
// Allow for...of. Edited from:
// https://github.com/airbnb/javascript/blob/b4377fb03089dd7f08955242695860d47f9caab4/packages/eslint-config-airbnb-base/rules/style.js#L333
"no-restricted-syntax": [
"error",
{
"selector": "ForInStatement",
"message": "for..in loops iterate over the entire prototype chain, which is virtually never what you want. Use Object.{keys,values,entries}, and iterate over the resulting array."
},
{
"selector": "LabeledStatement",
"message": "Labels are a form of GOTO; using them makes code confusing and hard to maintain and understand."
},
{
"selector": "WithStatement",
"message": "`with` is disallowed in strict mode because it makes code impossible to predict and optimize."
}
],
"import/no-extraneous-dependencies": "off",
"import/prefer-default-export": "off",
"import/named": "off",
@ -47,13 +68,22 @@
"react/jsx-one-expression-per-line": "off",
"react/button-has-type": "off",
"react/require-default-props": "off",
"react/function-component-definition": "off",
// Teact feature
"react/style-prop-object": "off",
"react/jsx-no-bind": ["error", {
"ignoreRefs": true,
"allowArrowFunctions": true,
"allowFunctions": true,
"allowBind": false,
"ignoreDOMComponents": true
}],
"jsx-a11y/click-events-have-key-events": "off",
"jsx-a11y/no-static-element-interactions": "off",
"jsx-a11y/label-has-associated-control": "off",
"jsx-a11y/anchor-is-valid": "off",
"jsx-a11y/no-noninteractive-element-to-interactive-role": "off",
"jsx-a11y/media-has-caption": "off",
"no-async-without-await/no-async-without-await": 1,
"@typescript-eslint/no-use-before-define": [
"error",
@ -64,6 +94,7 @@
"@typescript-eslint/camelcase": "off",
"@typescript-eslint/member-delimiter-style": "error",
"@typescript-eslint/default-param-last": "off",
"@typescript-eslint/return-await": ["error", "in-try-catch"],
"teactn/prefer-separate-component-file": "off"
},
"settings": {
@ -71,8 +102,5 @@
},
"parserOptions": {
"project": "./tsconfig.json"
},
"globals": {
"APP_REVISION": "readonly"
}
}

3443
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -34,36 +34,34 @@
"author": "Alexander Zinchuk (alexander@zinchuk.com)",
"license": "GPL-3.0-or-later",
"devDependencies": {
"@babel/core": "^7.17.0",
"@babel/core": "^7.17.5",
"@babel/plugin-proposal-class-properties": "^7.16.7",
"@babel/plugin-proposal-nullish-coalescing-operator": "^7.16.7",
"@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3",
"@babel/preset-env": "^7.16.11",
"@babel/preset-react": "^7.16.7",
"@babel/preset-typescript": "^7.16.7",
"@peculiar/webcrypto": "^1.2.3",
"@peculiar/webcrypto": "^1.3.2",
"@testing-library/jest-dom": "^5.16.2",
"@types/croppie": "^2.6.1",
"@types/css-font-loading-module": "0.0.7",
"@types/dom-mediacapture-record": "^1.0.11",
"@types/jest": "^27.4.0",
"@types/react": "^17.0.39",
"@types/react-dom": "^17.0.11",
"@types/resize-observer-browser": "^0.1.7",
"@types/jest": "^27.4.1",
"@types/react": "^17.0.40",
"@types/react-dom": "^17.0.13",
"@types/wicg-mediasession": "^1.1.3",
"@typescript-eslint/eslint-plugin": "^5.11.0",
"@typescript-eslint/parser": "^5.11.0",
"@typescript-eslint/eslint-plugin": "^5.14.0",
"@typescript-eslint/parser": "^5.14.0",
"@webpack-cli/serve": "^1.6.1",
"autoprefixer": "^10.4.2",
"babel-loader": "^8.2.3",
"browserlist": "^1.0.1",
"buffer": "^6.0.3",
"cross-env": "^7.0.3",
"css-loader": "^6.6.0",
"css-loader": "^6.7.1",
"css-minimizer-webpack-plugin": "^3.4.1",
"dotenv": "^16.0.0",
"eslint": "^8.8.0",
"eslint-config-airbnb-typescript": "^16.1.0",
"eslint": "^8.10.0",
"eslint-config-airbnb": "^19.0.4",
"eslint-config-airbnb-typescript": "^16.1.1",
"eslint-config-react-app": "^7.0.0",
"eslint-import-resolver-webpack": "^0.13.2",
"eslint-plugin-flowtype": "^8.0.3",
@ -71,30 +69,30 @@
"eslint-plugin-jsx-a11y": "^6.5.1",
"eslint-plugin-no-async-without-await": "^1.2.0",
"eslint-plugin-no-null": "^1.0.2",
"eslint-plugin-react": "^7.28.0",
"eslint-plugin-react": "^7.29.3",
"eslint-plugin-react-hooks": "^4.3.0",
"eslint-plugin-teactn": "git+https://github.com/korenskoy/eslint-plugin-teactn#c2c39dd005d58c07c24c4361de804dce1c6261b5",
"git-revision-webpack-plugin": "^5.0.0",
"html-webpack-plugin": "^5.5.0",
"husky": "^7.0.4",
"jest": "^27.5.0",
"jest": "^27.5.1",
"jest-raw-loader": "^1.0.1",
"lint-staged": "^12.3.3",
"mini-css-extract-plugin": "^2.5.3",
"lint-staged": "^12.3.5",
"mini-css-extract-plugin": "^2.6.0",
"postcss-loader": "^6.2.1",
"postcss-modules": "^4.3.0",
"postcss-modules": "^4.3.1",
"react": "^17.0.2",
"replace-in-file": "^6.3.2",
"sass": "^1.49.7",
"sass-loader": "^12.4.0",
"sass": "^1.49.9",
"sass-loader": "^12.6.0",
"style-loader": "^3.3.1",
"stylelint": "^14.3.0",
"stylelint": "^14.5.3",
"stylelint-config-recommended-scss": "^5.0.2",
"stylelint-declaration-block-no-ignored-properties": "^2.5.0",
"stylelint-group-selectors": "^1.0.8",
"stylelint-high-performance-animation": "^1.6.0",
"typescript": "^4.5.5",
"webpack": "^5.68.0",
"typescript": "^4.6.2",
"webpack": "^5.70.0",
"webpack-bundle-analyzer": "^4.5.0",
"webpack-cli": "^4.9.2",
"webpack-dev-server": "^4.7.4",

View File

@ -1,6 +1,6 @@
declare const process: NodeJS.Process;
declare var APP_REVISION: string;
declare const APP_REVISION: string;
declare namespace React {
interface HTMLAttributes {

View File

@ -598,7 +598,9 @@ function buildGeo(media: GramJs.MessageMediaGeo): ApiLocation | undefined {
}
function buildVenue(media: GramJs.MessageMediaVenue): ApiLocation | undefined {
const { geo, title, provider, address, venueId, venueType } = media;
const {
geo, title, provider, address, venueId, venueType,
} = media;
const point = buildGeoPoint(geo);
return point && {
type: 'venue',
@ -624,7 +626,9 @@ function buildGeoLive(media: GramJs.MessageMediaGeoLive): ApiLocation | undefine
function buildGeoPoint(geo: GramJs.TypeGeoPoint): ApiLocation['geo'] | undefined {
if (geo instanceof GramJs.GeoPointEmpty) return undefined;
const { long, lat, accuracyRadius, accessHash } = geo;
const {
long, lat, accuracyRadius, accessHash,
} = geo;
return {
long,
lat,

View File

@ -229,8 +229,19 @@ export function sendMessage(
onProgress?: ApiOnProgress,
) {
const localMessage = buildLocalMessage(
chat, text, entities, replyingTo, attachment, sticker, gif, poll, contact, groupedId, scheduledAt,
sendAs, serverTimeOffset,
chat,
text,
entities,
replyingTo,
attachment,
sticker,
gif,
poll,
contact,
groupedId,
scheduledAt,
sendAs,
serverTimeOffset,
);
onUpdate({
'@type': localMessage.isScheduled ? 'newScheduledMessage' : 'newMessage',

View File

@ -776,7 +776,6 @@ export function updater(update: Update, originRequest?: GramJs.AnyRequest) {
return;
}
if (_entities?.length) {
_entities
.filter((e) => e instanceof GramJs.User && !e.contact)

View File

@ -81,7 +81,7 @@ const CountryCodeInput: FC<OwnProps & StateProps> = ({
updateFilter(target.value);
}, [filter, updateFilter, value]);
const CodeInput: FC<{ onTrigger: () => void; isOpen?: boolean }> = ({ onTrigger, isOpen }) => {
const CodeInput: FC<{ onTrigger: () => void; isOpen?: boolean }> = useCallback(({ onTrigger, isOpen }) => {
const handleTrigger = () => {
if (isOpen) {
return;
@ -126,7 +126,7 @@ const CountryCodeInput: FC<OwnProps & StateProps> = ({
)}
</div>
);
};
}, [filter, handleInput, handleInputKeyDown, id, isLoading, lang, value]);
return (
<DropdownMenu

View File

@ -280,34 +280,32 @@ const Audio: FC<OwnProps> = ({
function renderWithTitle() {
return (
<>
<div className={contentClassName}>
<div className="content-row">
<p className="title" dir="auto" title={renderFirstLine()}>{renderText(renderFirstLine())}</p>
<div className={contentClassName}>
<div className="content-row">
<p className="title" dir="auto" title={renderFirstLine()}>{renderText(renderFirstLine())}</p>
<div className="message-date">
{date && (
<Link
className="date"
onClick={handleDateClick}
>
{formatPastTimeShort(lang, date * 1000)}
</Link>
)}
</div>
<div className="message-date">
{date && (
<Link
className="date"
onClick={handleDateClick}
>
{formatPastTimeShort(lang, date * 1000)}
</Link>
)}
</div>
{withSeekline && (
<div className="meta search-result" dir={isRtl ? 'rtl' : undefined}>
<span className="duration with-seekline" dir="auto">
{playProgress < 1 && `${formatMediaDuration(duration * playProgress, duration)}`}
</span>
{renderSeekline(playProgress, bufferedProgress, seekerRef)}
</div>
)}
{!withSeekline && renderSecondLine()}
</div>
</>
{withSeekline && (
<div className="meta search-result" dir={isRtl ? 'rtl' : undefined}>
<span className="duration with-seekline" dir="auto">
{playProgress < 1 && `${formatMediaDuration(duration * playProgress, duration)}`}
</span>
{renderSeekline(playProgress, bufferedProgress, seekerRef)}
</div>
)}
{!withSeekline && renderSecondLine()}
</div>
);
}
@ -356,8 +354,17 @@ const Audio: FC<OwnProps> = ({
)}
{origin === AudioOrigin.Search && renderWithTitle()}
{origin !== AudioOrigin.Search && audio && renderAudio(
lang, audio, duration, isPlaying, playProgress, bufferedProgress, seekerRef,
(isDownloading || isUploading), date, transferProgress, onDateClick ? handleDateClick : undefined,
lang,
audio,
duration,
isPlaying,
playProgress,
bufferedProgress,
seekerRef,
(isDownloading || isUploading),
date,
transferProgress,
onDateClick ? handleDateClick : undefined,
)}
{origin === AudioOrigin.SharedMedia && (voice || video) && renderWithTitle()}
{origin === AudioOrigin.Inline && voice && (

View File

@ -1,4 +1,6 @@
import React, { FC, memo, useCallback, useRef } from '../../lib/teact/teact';
import React, {
FC, memo, useCallback, useRef,
} from '../../lib/teact/teact';
import { ApiMessage } from '../../api/types';

View File

@ -45,7 +45,7 @@ export function renderMessageSummary(
highlight?: string,
truncateLength = TRUNCATED_SUMMARY_LENGTH,
): TextPart[] {
let { entities } = message.content.text || {};
const { entities } = message.content.text || {};
const hasSpoilers = entities?.some((e) => e.type === ApiMessageEntityTypes.Spoiler);
if (!hasSpoilers) {

View File

@ -104,7 +104,9 @@ const SettingsFoldersChatFilters: FC<OwnProps> = ({
}, [mode, selectedChatIds, dispatch]);
useHistoryBack(
isActive, onReset, onScreenSelect,
isActive,
onReset,
onScreenSelect,
mode === 'included' ? SettingsScreens.FoldersIncludedChats : SettingsScreens.FoldersExcludedChats,
);

View File

@ -163,17 +163,15 @@ const MediaViewerActions: FC<OwnProps & StateProps> = ({
return (
<div className="MediaViewerActions">
{!isAvatar && !isProtected && (
<>
<Button
round
size="smaller"
color="translucent-white"
ariaLabel={lang('Forward')}
onClick={onForward}
>
<i className="icon-forward" />
</Button>
</>
<Button
round
size="smaller"
color="translucent-white"
ariaLabel={lang('Forward')}
onClick={onForward}
>
<i className="icon-forward" />
</Button>
)}
{renderDownloadButton()}
<Button

View File

@ -518,7 +518,8 @@ const MediaViewerSlides: FC<OwnProps> = ({
<MediaViewerContent
/* eslint-disable-next-line react/jsx-props-no-spreading */
{...rest}
messageId={previousMessageId} />
messageId={previousMessageId}
/>
</div>
)}
{activeMessageId && (
@ -543,7 +544,8 @@ const MediaViewerSlides: FC<OwnProps> = ({
<MediaViewerContent
/* eslint-disable-next-line react/jsx-props-no-spreading */
{...rest}
messageId={nextMessageId}/>
messageId={nextMessageId}
/>
</div>
)}
</div>
@ -571,4 +573,5 @@ function checkIfControlTarget(e: TouchEvent | MouseEvent) {
e.preventDefault();
return true;
}
return false;
}

View File

@ -1,12 +1,16 @@
import React, {
FC, memo, useCallback, useEffect, useRef, useState,
} from '../../lib/teact/teact';
import { ApiDimensions } from '../../api/types';
import useBuffering from '../../hooks/useBuffering';
import useFullscreenStatus from '../../hooks/useFullscreen';
import useShowTransition from '../../hooks/useShowTransition';
import useVideoCleanup from '../../hooks/useVideoCleanup';
import React, { FC, memo, useCallback, useEffect, useRef, useState } from '../../lib/teact/teact';
import { IS_IOS, IS_SINGLE_COLUMN_LAYOUT, IS_TOUCH_ENV } from '../../util/environment';
import safePlay from '../../util/safePlay';
import Button from '../ui/Button';
import ProgressSpinner from '../ui/ProgressSpinner';

View File

@ -55,12 +55,11 @@ const VideoPlayerControls: FC<IProps> = ({
const isSeekingRef = useRef<boolean>(false);
const isSeeking = isSeekingRef.current;
useEffect(() => {
let timeout: number | undefined;
if (!isVisible || !isPlayed || isSeeking) {
if (timeout) window.clearTimeout(timeout);
return;
return undefined;
}
timeout = window.setTimeout(() => {
setVisibility(false);
@ -113,7 +112,6 @@ const VideoPlayerControls: FC<IProps> = ({
});
}, [isVisible, handleStartSeek, handleSeek, handleStopSeek]);
return (
<div
className={buildClassName('VideoPlayerControls', isForceMobileVersion && 'mobile', isVisible && 'active')}

View File

@ -6,8 +6,9 @@ import useModuleLoader from '../../hooks/useModuleLoader';
const EmojiInteractionAnimationAsync: FC<OwnProps> = (props) => {
const { activeEmojiInteraction } = props;
const EmojiInteractionAnimation = useModuleLoader(Bundles.Extra, 'EmojiInteractionAnimation',
!activeEmojiInteraction);
const EmojiInteractionAnimation = useModuleLoader(
Bundles.Extra, 'EmojiInteractionAnimation', !activeEmojiInteraction,
);
// eslint-disable-next-line react/jsx-props-no-spreading
return EmojiInteractionAnimation ? <EmojiInteractionAnimation {...props} /> : undefined;

View File

@ -354,7 +354,10 @@ const MiddleColumn: FC<StateProps> = ({
useHistoryBack(
renderingChatId && renderingThreadId,
closeChat, undefined, undefined, undefined,
closeChat,
undefined,
undefined,
undefined,
messageLists?.map(createMessageHash) || [],
);

View File

@ -1,4 +1,6 @@
import React, { FC, memo, useCallback, useState } from '../../lib/teact/teact';
import React, {
FC, memo, useCallback, useState,
} from '../../lib/teact/teact';
import { withGlobal, getDispatch } from '../../lib/teact/teactn';
import { ApiUser } from '../../api/types';
@ -61,10 +63,9 @@ const UserReportPanel: FC<OwnProps & StateProps> = ({ userId, user }) => {
]);
if (!settings) {
return;
return undefined;
}
return (
<div className="UserReportPanel">
{canAddContact && (

View File

@ -57,7 +57,7 @@ const AttachMenu: FC<OwnProps> = ({
const lang = useLang();
if (!isButtonVisible) {
return;
return undefined;
}
return (

View File

@ -27,6 +27,7 @@ const StickerSetCover: FC<OwnProps> = ({ stickerSet, observeIntersection }) => {
const firstLetters = useMemo(() => {
if ((isVideo && !IS_WEBM_SUPPORTED) || !mediaData) return getFirstLetters(stickerSet.title, 2);
return undefined;
}, [isVideo, mediaData, stickerSet.title]);
return (

View File

@ -2,7 +2,7 @@ export default async function getFilesFromDataTransferItems(dataTransferItems: D
const files: File[] = [];
function traverseFileTreePromise(entry: FileSystemEntry | File, item: DataTransferItem) {
return new Promise(resolve => {
return new Promise((resolve) => {
if (entry instanceof File) {
files.push(entry);
resolve(entry);
@ -20,11 +20,11 @@ export default async function getFilesFromDataTransferItems(dataTransferItems: D
resolve(itemFile);
});
} else if (entry.isDirectory) {
let dirReader = (entry as FileSystemDirectoryEntry).createReader();
const dirReader = (entry as FileSystemDirectoryEntry).createReader();
dirReader.readEntries((entries) => {
let entriesPromises = [];
for (let entr of entries) {
entriesPromises.push(traverseFileTreePromise(entr, item));
const entriesPromises = [];
for (let i = 0; i < entries.length; i++) {
entriesPromises.push(traverseFileTreePromise(entries[i], item));
}
resolve(Promise.all(entriesPromises));
});
@ -32,8 +32,9 @@ export default async function getFilesFromDataTransferItems(dataTransferItems: D
});
}
let entriesPromises = [];
for (let item of dataTransferItems) {
const entriesPromises = [];
for (let i = 0; i < dataTransferItems.length; i++) {
const item = dataTransferItems[i];
if (item.kind === 'file') {
const entry = item.webkitGetAsEntry() || item.getAsFile();
if (entry) {
@ -46,4 +47,3 @@ export default async function getFilesFromDataTransferItems(dataTransferItems: D
return files;
}

View File

@ -24,7 +24,6 @@ const useClipboardPaste = (
return;
}
const pastedText = e.clipboardData.getData('text').substring(0, MAX_MESSAGE_LENGTH);
const { items } = e.clipboardData;
let files: File[] = [];

View File

@ -36,10 +36,10 @@ const prepareLibraryMemo = memoized(prepareLibrary);
const searchInLibraryMemo = memoized(searchInLibrary);
try {
RE_EMOJI_SEARCH = new RegExp('(^|\\s):[-+_:\\p{L}\\p{N}]*$', 'gui');
RE_EMOJI_SEARCH = /(^|\s):[-+_:\p{L}\p{N}]*$/gui;
} catch (e) {
// Support for older versions of firefox
RE_EMOJI_SEARCH = new RegExp('(^|\\s):[-+_:\\d\\wа-яё]*$', 'gi');
RE_EMOJI_SEARCH = /(^|\s):[-+_:\d\wа-яё]*$/gi;
}
export default function useEmojiTooltip(

View File

@ -16,10 +16,10 @@ const runThrottled = throttle((cb) => cb(), 500, true);
let RE_USERNAME_SEARCH: RegExp;
try {
RE_USERNAME_SEARCH = new RegExp('(^|\\s)@[-_\\p{L}\\p{M}\\p{N}]*$', 'gui');
RE_USERNAME_SEARCH = /(^|\s)@[-_\p{L}\p{M}\p{N}]*$/gui;
} catch (e) {
// Support for older versions of firefox
RE_USERNAME_SEARCH = new RegExp('(^|\\s)@[-_\\d\\wа-яё]*$', 'gi');
RE_USERNAME_SEARCH = /(^|\s)@[-_\d\wа-яё]*$/gi;
}
export default function useMentionTooltip(

View File

@ -19,7 +19,9 @@ import { formatCountdownShort, formatLastUpdated } from '../../../util/dateForma
import useLang from '../../../hooks/useLang';
import useForceUpdate from '../../../hooks/useForceUpdate';
import useTimeout from '../../../hooks/useTimeout';
import { getMetersPerPixel, getVenueColor, getVenueIconUrl, prepareMapUrl } from '../../../util/map';
import {
getMetersPerPixel, getVenueColor, getVenueIconUrl, prepareMapUrl,
} from '../../../util/map';
import buildClassName from '../../../util/buildClassName';
import usePrevious from '../../../hooks/usePrevious';
import useInterval from '../../../hooks/useInterval';
@ -28,7 +30,7 @@ import { getServerTime } from '../../../util/serverTime';
import Avatar from '../../common/Avatar';
import Skeleton from '../../ui/Skeleton';
import mapPin from '/src/assets/map-pin.svg';
import mapPin from '../../../assets/map-pin.svg';
import './Location.scss';
const MOVE_THRESHOLD = 0.0001; // ~11m
@ -79,7 +81,9 @@ const Location: FC<OwnProps> = ({
const [point, setPoint] = useState(geo);
const shouldRenderText = type === 'venue' || (type === 'geoLive' && !isExpired);
const { width, height, zoom, scale } = DEFAULT_MAP_CONFIG;
const {
width, height, zoom, scale,
} = DEFAULT_MAP_CONFIG;
const mediaHash = Boolean(lastSyncTime) && buildStaticMapHash(point, width, height, zoom, scale);
const mediaBlobUrl = useMedia(mediaHash);
@ -206,13 +210,15 @@ const Location: FC<OwnProps> = ({
function renderMap() {
if (!mapBlobUrl) return <Skeleton width={width} height={height} />;
return <img
className="full-media map"
src={mapBlobUrl}
alt="Location on a map"
width={DEFAULT_MAP_CONFIG.width}
height={DEFAULT_MAP_CONFIG.height}
/>;
return (
<img
className="full-media map"
src={mapBlobUrl}
alt="Location on a map"
width={DEFAULT_MAP_CONFIG.width}
height={DEFAULT_MAP_CONFIG.height}
/>
);
}
function renderPin() {
@ -279,4 +285,3 @@ const Location: FC<OwnProps> = ({
};
export default memo(Location);

View File

@ -964,7 +964,9 @@ const Message: FC<OwnProps & StateProps> = ({
export default memo(withGlobal<OwnProps>(
(global, ownProps): StateProps => {
const { focusedMessage, forwardMessages, lastSyncTime, serverTimeOffset } = global;
const {
focusedMessage, forwardMessages, lastSyncTime, serverTimeOffset,
} = global;
const {
message, album, withSenderName, withAvatar, threadId, messageListType, isLastInDocumentGroup,
} = ownProps;

View File

@ -25,7 +25,9 @@ type OwnProps = {
const cn = createClassNameBuilder('ReactionSelectorReaction');
const ReactionSelectorReaction: FC<OwnProps> = ({ reaction, previewIndex, onSendReaction, isReady }) => {
const ReactionSelectorReaction: FC<OwnProps> = ({
reaction, previewIndex, onSendReaction, isReady,
}) => {
// eslint-disable-next-line no-null/no-null
const containerRef = useRef<HTMLDivElement>(null);

View File

@ -41,16 +41,17 @@ export default function withSelectControl(WrappedComponent: FC) {
}, [toggleMessageSelection, message]);
const newProps = useMemo(() => {
const { dimensions: dims, onClick } = props;
return {
...props,
isInSelectMode,
isSelected,
dimensions: {
...props.dimensions,
...dims,
x: 0,
y: 0,
},
onClick: isInSelectMode ? undefined : props.onClick,
onClick: isInSelectMode ? undefined : onClick,
};
}, [props, isInSelectMode, isSelected]);

View File

@ -167,8 +167,21 @@ const Profile: FC<OwnProps & StateProps> = ({
const tabType = tabs[activeTab].type as ProfileTabType;
const [resultType, viewportIds, getMore, noProfileInfo] = useProfileViewportIds(
isRightColumnShown, loadMoreMembers, loadCommonChats, searchMediaMessagesLocal, tabType, mediaSearchType, members,
commonChatIds, usersById, userStatusesById, chatsById, chatMessages, foundIds, chatId, lastSyncTime,
isRightColumnShown,
loadMoreMembers,
loadCommonChats,
searchMediaMessagesLocal,
tabType,
mediaSearchType,
members,
commonChatIds,
usersById,
userStatusesById,
chatsById,
chatMessages,
foundIds,
chatId,
lastSyncTime,
serverTimeOffset,
);
const activeKey = tabs.findIndex(({ type }) => type === resultType);

View File

@ -211,7 +211,7 @@ const RightColumn: FC<StateProps> = ({
// We need to clear profile state and management screen state, when changing chats
useLayoutEffectWithPrevDeps(([prevChatId]) => {
if (prevChatId !== chatId ) {
if (prevChatId !== chatId) {
setProfileState(ProfileState.Profile);
setManagementScreen(ManagementScreens.Initial);
}

View File

@ -295,7 +295,7 @@ const ManageInvites: FC<OwnProps & StateProps> = ({
/>
)}
</div>
<p className='text-muted'>{isChannel ? lang('PrimaryLinkHelpChannel') : lang('PrimaryLinkHelp')}</p>
<p className="text-muted">{isChannel ? lang('PrimaryLinkHelpChannel') : lang('PrimaryLinkHelp')}</p>
</div>
{primaryInviteLink && (
<div className="section">

View File

@ -148,7 +148,7 @@ const Statistics: FC<OwnProps & StateProps> = ({
))}
</div>
{Boolean(statistics.recentTopMessages?.length) &&
{Boolean(statistics.recentTopMessages?.length) && (
<div className="Statistics--messages">
<h2 className="Statistics--messages-title">{lang('ChannelStats.Recent.Header')}</h2>
@ -156,7 +156,7 @@ const Statistics: FC<OwnProps & StateProps> = ({
<StatisticsRecentMessage message={message as ApiMessage & StatisticsRecentMessageType} />
))}
</div>
}
)}
</div>
);
};

View File

@ -44,7 +44,7 @@ const StatisticsRecentMessage: FC<OwnProps> = ({ message }) => {
{formatDateTimeToString(message.date * 1000, lang.code)}
</div>
<div className="StatisticsRecentMessage--meta">
{Boolean(message.forwards) ? lang('ChannelStats.SharesCount', message.forwards) : 'No shares'}
{message.forwards ? lang('ChannelStats.SharesCount', message.forwards) : 'No shares'}
</div>
</div>
</p>

View File

@ -137,7 +137,6 @@ const Button: FC<OwnProps> = ({
dir={isRtl ? 'rtl' : undefined}
aria-label={ariaLabel}
aria-controls={ariaControls}
aria-haspopup={hasPopup}
>
{children}
{!disabled && ripple && (
@ -148,7 +147,6 @@ const Button: FC<OwnProps> = ({
}
return (
// eslint-disable-next-line react/button-has-type
<button
ref={elementRef as RefObject<HTMLButtonElement>}
id={id}

View File

@ -1,4 +1,6 @@
import React, { FC, memo, useCallback, useRef } from '../../lib/teact/teact';
import React, {
FC, memo, useCallback, useRef,
} from '../../lib/teact/teact';
import useLang from '../../hooks/useLang';
import { TextPart } from '../common/helpers/renderMessageText';

View File

@ -1,4 +1,6 @@
import { FC, useRef, useLayoutEffect, VirtualElement } from '../../lib/teact/teact';
import {
FC, useRef, useLayoutEffect, VirtualElement,
} from '../../lib/teact/teact';
import TeactDOM from '../../lib/teact/teact-dom';
type OwnProps = {

View File

@ -4,7 +4,6 @@ import useShowTransition from '../../hooks/useShowTransition';
import usePrevious from '../../hooks/usePrevious';
import buildClassName from '../../util/buildClassName';
type OwnProps = {
isOpen: boolean;
isCustom?: boolean;

View File

@ -12,7 +12,9 @@ type OwnProps = {
className?: string;
};
const Skeleton: FC<OwnProps> = ({ variant = 'rectangular', animation = 'wave', width, height, className }) => {
const Skeleton: FC<OwnProps> = ({
variant = 'rectangular', animation = 'wave', width, height, className,
}) => {
const classNames = buildClassName('Skeleton', variant, animation, className);
const style = (width ? `width: ${width}px;` : '') + (height ? `height: ${height}px;` : '');
return (

View File

@ -260,7 +260,8 @@ const Transition: FC<TransitionProps> = ({
typeof render === 'function'
? render(key === activeKey, key === prevActiveKey, activeKey)
: render
}</div>
}
</div>
);
});

View File

@ -39,7 +39,7 @@ export const useResize = (
}
useEffect(() => {
if (!isActive) return;
if (!isActive) return undefined;
const handleMouseMove = (event: MouseEvent) => {
const newWidth = Math.ceil(initialElementWidth + event.clientX - initialMouseX);

View File

@ -1,29 +1,34 @@
{
"rules": {
"indent": [
"error",
4,
{
"SwitchCase": 1
}
],
"@typescript-eslint/indent": [
"error",
4,
{
"SwitchCase": 1
}
],
"max-len": [
"error",
120
],
"no-bitwise": "off",
"no-underscore-dangle": "off",
"no-continue": "off",
"no-restricted-syntax": "off",
"class-methods-use-this": "off",
"max-classes-per-file": "off",
"camelcase": "error"
}
"root": true,
"parser": "@typescript-eslint/parser",
"plugins": [
"@typescript-eslint"
],
"rules": {
"indent": [
"error",
4,
{
"SwitchCase": 1
}
],
"@typescript-eslint/indent": [
"error",
4,
{
"SwitchCase": 1
}
],
"max-len": [
"error",
120
],
"no-bitwise": "off",
"no-underscore-dangle": "off",
"no-continue": "off",
"no-restricted-syntax": "off",
"class-methods-use-this": "off",
"max-classes-per-file": "off",
"camelcase": "off"
}
}

View File

@ -186,7 +186,6 @@ class TelegramClient {
}
// set defaults vars
this._sender.userDisconnected = false;
// eslint-disable-next-line camelcase
this._sender._user_connected = false;
this._sender.isReconnecting = false;
this._sender._disconnected = true;

View File

@ -31,11 +31,11 @@
@keyframes lovely-chart--animation-fadeIn {
from {
opacity: 0;
transform: scale3d(.5, .5, 1);
transform: scale3d(0.5, 0.5, 1);
}
40% {
transform: scale3d(.75, .75, 1);
transform: scale3d(0.75, 0.75, 1);
}
to {
@ -51,12 +51,12 @@
}
60% {
transform: scale3d(.75, .75, 1);
transform: scale3d(0.75, 0.75, 1);
}
to {
opacity: 0;
transform: scale3d(.5, .5, 1);
transform: scale3d(0.5, 0.5, 1);
}
}

View File

@ -1,6 +1,5 @@
.lovely-chart--button {
border: 1px solid #E6ECF0;
background-color: transparent;
padding: 7px 7px;
display: inline-block;
border-radius: 18px;
@ -11,7 +10,7 @@
position: relative;
border-color: var(--text-color);
background: transparent;
background-color: transparent;
color: var(--text-color);
&.lovely-chart--state-checked {

View File

@ -169,7 +169,8 @@ addReducer('openTipsChat', (global, actions, payload) => {
addReducer('loadAllChats', (global, actions, payload) => {
const listType = payload.listType as 'active' | 'archived';
let { shouldReplace, onReplace } = payload;
const { onReplace } = payload;
let { shouldReplace } = payload;
let i = 0;
(async () => {

View File

@ -34,7 +34,9 @@ addReducer('loadStatistics', (global, actions, payload) => {
});
addReducer('loadStatisticsAsyncGraph', (global, actions, payload) => {
const { chatId, token, name, isPercentage } = payload;
const {
chatId, token, name, isPercentage,
} = payload;
const chat = selectChat(global, chatId);
if (!chat?.fullInfo) {
return;

View File

@ -43,8 +43,9 @@ addReducer('apiUpdate', (global, actions, update: ApiUpdate) => {
global = getGlobal();
participants.forEach((participant) => {
if (participant.id) {
global = updateGroupCallParticipant(global, groupCallId, participant.id, participant,
Boolean(nextOffset) || currentUserId === participant.id);
global = updateGroupCallParticipant(
global, groupCallId, participant.id, participant, Boolean(nextOffset) || currentUserId === participant.id,
);
}
});
if (nextOffset) {

View File

@ -687,7 +687,7 @@ addReducer('copySelectedMessages', (global) => {
copyTextForMessages(global, chatId, messageIds);
});
addReducer('copyMessagesByIds', (global, actions, payload: { messageIds?: number[] } ) => {
addReducer('copyMessagesByIds', (global, actions, payload: { messageIds?: number[] }) => {
const { messageIds } = payload;
const chat = selectCurrentChat(global);
if (!messageIds || messageIds.length === 0 || !chat) {
@ -697,7 +697,6 @@ addReducer('copyMessagesByIds', (global, actions, payload: { messageIds?: number
copyTextForMessages(global, chat.id, messageIds);
});
function copyTextForMessages(global: GlobalState, chatId: string, messageIds: number[]) {
const { threadId } = selectCurrentMessageList(global) || {};
const lang = langProvider.getTranslation;
@ -712,7 +711,7 @@ function copyTextForMessages(global: GlobalState, chatId: string, messageIds: nu
const result = messages.reduce((acc, message) => {
const sender = selectSender(global, message);
acc.push(`> ${sender ? getSenderTitle(lang, sender) : ''}:`);
acc.push(getMessageSummaryText(lang, message, false, 0, undefined, true) + '\n');
acc.push(`${getMessageSummaryText(lang, message, false, 0, undefined, true)}\n`);
return acc;
}, [] as string[]);

View File

@ -134,7 +134,9 @@ export function buildStaticMapHash(
zoom: number,
scale: number,
) {
const { long, lat, accessHash, accuracyRadius } = geo;
const {
long, lat, accessHash, accuracyRadius,
} = geo;
// eslint-disable-next-line max-len
return `staticMap:${accessHash}?lat=${lat}&long=${long}&w=${width}&h=${height}&zoom=${zoom}&scale=${scale}&accuracyRadius=${accuracyRadius}`;

View File

@ -48,7 +48,6 @@ export function getMessageTextWithSpoilers(message: ApiMessage) {
const spoiler = generateBrailleSpoiler(length);
return `${accText.substr(0, offset)}${spoiler}${accText.substr(offset + length, accText.length)}`;
}, text);
}
@ -160,12 +159,12 @@ export function getMessageSummaryDescription(
}
if (invoice) {
summary = lang('PaymentInvoice') + ': ' + invoice.text;
summary = `${lang('PaymentInvoice')}: ${invoice.text}`;
}
if (text) {
if (isExtended && summary) {
summary += '\n' + truncatedText;
summary += `\n${truncatedText}`;
} else {
summary = truncatedText;
}

View File

@ -8,10 +8,10 @@ export function checkIfReactionAdded(oldReactions?: ApiReactions, newReactions?:
if (!oldReactions || !oldReactions.recentReactions) return true;
if (!newReactions || !newReactions.recentReactions) return false;
// Skip reactions from yourself
if (newReactions.recentReactions.every(reaction => reaction.userId === currentUserId)) return false;
if (newReactions.recentReactions.every((reaction) => reaction.userId === currentUserId)) return false;
const oldReactionsMap = oldReactions.results.reduce<Record<string, number>>((acc, reaction) => {
acc[reaction.reaction] = reaction.count;
return acc;
}, {});
return newReactions.results.some(r => !oldReactionsMap[r.reaction] || oldReactionsMap[r.reaction] < r.count);
return newReactions.results.some((r) => !oldReactionsMap[r.reaction] || oldReactionsMap[r.reaction] < r.count);
}

View File

@ -219,7 +219,7 @@ export function leaveChat(global: GlobalState, leftChatId: string): GlobalState
export function addChatMembers(global: GlobalState, chat: ApiChat, membersToAdd: ApiChatMember[]): GlobalState {
const currentMembers = chat.fullInfo?.members;
const newMemberIds = new Set(membersToAdd.map(m => m.userId));
const newMemberIds = new Set(membersToAdd.map((m) => m.userId));
const updatedMembers = [
...currentMembers?.filter((m) => !newMemberIds.has(m.userId)) || [],
...membersToAdd,

View File

@ -1,6 +1,6 @@
import { DEBUG } from '../config';
export const CLIPBOARD_ITEM_SUPPORTED = navigator.clipboard && window.ClipboardItem;
export const CLIPBOARD_ITEM_SUPPORTED = window.navigator.clipboard && window.ClipboardItem;
const textCopyEl = document.createElement('textarea');
textCopyEl.setAttribute('readonly', '');
@ -51,7 +51,7 @@ async function copyBlobToClipboard(pngBlob: Blob | null) {
}
try {
await navigator.clipboard.write?.([
await window.navigator.clipboard.write?.([
new ClipboardItem({
[pngBlob.type]: pngBlob,
}),

View File

@ -55,7 +55,7 @@ export const IS_TABLET_COLUMN_LAYOUT = !IS_SINGLE_COLUMN_LAYOUT && (
window.innerWidth <= MIN_SCREEN_WIDTH_FOR_STATIC_LEFT_COLUMN
);
export const IS_VOICE_RECORDING_SUPPORTED = Boolean(
navigator.mediaDevices && 'getUserMedia' in navigator.mediaDevices && (
window.navigator.mediaDevices && 'getUserMedia' in window.navigator.mediaDevices && (
window.AudioContext || (window as any).webkitAudioContext
),
);

View File

@ -203,9 +203,12 @@ function updateFolderManager(global: GlobalState) {
updateFolders(global, isAllFolderChanged, isArchivedFolderChanged, areFoldersChanged);
affectedFolderIds = affectedFolderIds.concat(updateChats(
global, areFoldersChanged || isAllFolderChanged || isArchivedFolderChanged,
areNotifySettingsChanged, areNotifyExceptionsChanged,
prevAllFolderListIds, prevArchivedFolderListIds,
global,
areFoldersChanged || isAllFolderChanged || isArchivedFolderChanged,
areNotifySettingsChanged,
areNotifyExceptionsChanged,
prevAllFolderListIds,
prevArchivedFolderListIds,
));
updateResults(unique(affectedFolderIds));
@ -315,7 +318,7 @@ function buildFolderSummaryFromMainList(
return {
id: folderId,
listIds: new Set(listIds),
orderedPinnedIds: orderedPinnedIds,
orderedPinnedIds,
pinnedChatIds: new Set(orderedPinnedIds),
};
}

View File

@ -4,7 +4,7 @@ export default function getMessageIdsForSelectedText() {
const selection = window.getSelection();
let selectedFragments = selection && selection.rangeCount ? selection.getRangeAt(0).cloneContents() : undefined;
if (!selectedFragments || selectedFragments.childElementCount === 0) {
return;
return undefined;
}
const messageIds = Array.from(selectedFragments.children)
@ -19,7 +19,6 @@ export default function getMessageIdsForSelectedText() {
return result;
}, [] as number[]);
// Cleanup a document fragment because it is playing media content in the background
while (selectedFragments.firstChild) {
selectedFragments.removeChild(selectedFragments.firstChild);

View File

@ -5,23 +5,23 @@ const PROVIDER = 'http://maps.google.com/maps';
const VENUE_COLORS = new Map(Object.entries({
'building/medical': '#43b3f4',
'building/gym': '#43b3f4',
'arts_entertainment': '#e56dd6',
'education/cafeteria': '#f7943f',
'travel/bedandbreakfast': '#9987ff',
'travel/hotel': '#9987ff',
'travel/hostel': '#9987ff',
'travel/resort': '#9987ff',
'building': '#6e81b2',
'education': '#a57348',
'event': '#959595',
'food': '#f7943f',
'education/cafeteria': '#f7943f',
'nightlife': '#e56dd6',
'travel/hotel_bar': '#e56dd6',
'parks_outdoors': '#6cc039',
'shops': '#ffb300',
'travel': '#1c9fff',
'work': '#ad7854',
'home': '#00aeef',
arts_entertainment: '#e56dd6',
building: '#6e81b2',
education: '#a57348',
event: '#959595',
food: '#f7943f',
home: '#00aeef',
nightlife: '#e56dd6',
parks_outdoors: '#6cc039',
shops: '#ffb300',
travel: '#1c9fff',
work: '#ad7854',
}));
const RANDOM_COLORS = [
@ -34,7 +34,7 @@ export function prepareMapUrl(lat: number, long: number, zoom: number) {
export function getMetersPerPixel(lat: number, zoom: number) {
// https://groups.google.com/g/google-maps-js-api-v3/c/hDRO4oHVSeM/m/osOYQYXg2oUJ
return 156543.03392 * Math.cos(lat * Math.PI / 180) / Math.pow(2, zoom);
return (156543.03392 * Math.cos(lat * (Math.PI / 180))) / 2 ** zoom;
}
export function getVenueIconUrl(type?: string) {
@ -52,6 +52,7 @@ export function getVenueColor(type?: string) {
}
function stringToNumber(str: string) {
return str.split('').reduce((prevHash, currVal) =>
(((prevHash << 5) - prevHash) + currVal.charCodeAt(0)) | 0, 0);
return str.split('').reduce((prevHash, currVal) => (
// eslint-disable-next-line no-bitwise
(((prevHash << 5) - prevHash) + currVal.charCodeAt(0)) | 0), 0);
}

View File

@ -1,5 +1,7 @@
import { callApi } from '../api/gramjs';
import { ApiChat, ApiMediaFormat, ApiMessage, ApiUser, ApiUserReaction } from '../api/types';
import {
ApiChat, ApiMediaFormat, ApiMessage, ApiUser, ApiUserReaction,
} from '../api/types';
import { renderActionMessageText } from '../components/common/helpers/renderActionMessageText';
import { DEBUG, IS_TEST } from '../config';
import { getDispatch, getGlobal, setGlobal } from '../lib/teact/teactn';
@ -264,9 +266,11 @@ function checkIfShouldNotify(chat: ApiChat) {
function getNotificationContent(chat: ApiChat, message: ApiMessage, reaction?: ApiUserReaction) {
const global = getGlobal();
const {
replyToMessageId,
} = message;
let {
senderId,
replyToMessageId,
} = message;
if (reaction) senderId = reaction.userId;

View File

@ -1,10 +1,10 @@
let RE_NOT_LETTER: RegExp;
try {
RE_NOT_LETTER = new RegExp('[^\\p{L}\\p{M}]+', 'ui');
RE_NOT_LETTER = /[^\p{L}\p{M}]+/ui;
} catch (e) {
// Support for older versions of firefox
RE_NOT_LETTER = new RegExp('[^\\wа-яё]+', 'i');
RE_NOT_LETTER = /[^\wа-яё]+/i;
}
export default function searchWords(haystack: string, needle: string | string[]) {

View File

@ -52,7 +52,7 @@ if (IS_SERVICE_WORKER_SUPPORTED) {
// eslint-disable-next-line no-console
console.log('[SW] Hard reload detected, re-enabling Service Worker');
}
await Promise.all(registrations.map(r => r.unregister()));
await Promise.all(registrations.map((r) => r.unregister()));
}
}