Merge pull request #5155 from matrix-org/t3chguy/lint-ts

Fix eslint ts override tsx matching and delint
This commit is contained in:
Michael Telatynski 2020-09-01 16:44:22 +01:00 committed by GitHub
commit 7c4a84aae0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
54 changed files with 257 additions and 242 deletions

View file

@ -19,7 +19,7 @@ module.exports = {
},
overrides: [{
"files": ["src/**/*.{ts, tsx}"],
"files": ["src/**/*.{ts,tsx}"],
"extends": ["matrix-org/ts"],
"rules": {
// We disable this while we're transitioning

View file

@ -70,6 +70,7 @@ interface IContent {
interface IThumbnail {
info: {
// eslint-disable-next-line camelcase
thumbnail_info: {
w: number;
h: number;
@ -104,7 +105,12 @@ interface IAbortablePromise<T> extends Promise<T> {
* @return {Promise} A promise that resolves with an object with an info key
* and a thumbnail key.
*/
function createThumbnail(element: ThumbnailableElement, inputWidth: number, inputHeight: number, mimeType: string): Promise<IThumbnail> {
function createThumbnail(
element: ThumbnailableElement,
inputWidth: number,
inputHeight: number,
mimeType: string,
): Promise<IThumbnail> {
return new Promise((resolve) => {
let targetWidth = inputWidth;
let targetHeight = inputHeight;
@ -437,11 +443,13 @@ export default class ContentMessages {
for (let i = 0; i < okFiles.length; ++i) {
const file = okFiles[i];
if (!uploadAll) {
const {finished} = Modal.createTrackedDialog<[boolean, boolean]>('Upload Files confirmation', '', UploadConfirmDialog, {
file,
currentIndex: i,
totalFiles: okFiles.length,
});
const {finished} = Modal.createTrackedDialog<[boolean, boolean]>('Upload Files confirmation',
'', UploadConfirmDialog, {
file,
currentIndex: i,
totalFiles: okFiles.length,
},
);
const [shouldContinue, shouldUploadAll] = await finished;
if (!shouldContinue) break;
if (shouldUploadAll) {

View file

@ -339,33 +339,9 @@ class HtmlHighlighter extends BaseHighlighter<string> {
}
}
class TextHighlighter extends BaseHighlighter<React.ReactNode> {
private key = 0;
/* create a <span> node to hold the given content
*
* snippet: content of the span
* highlight: true to highlight as a search match
*
* returns a React node
*/
protected processSnippet(snippet: string, highlight: boolean): React.ReactNode {
const key = this.key++;
let node = <span key={key} className={highlight ? this.highlightClass : null}>
{ snippet }
</span>;
if (highlight && this.highlightLink) {
node = <a key={key} href={this.highlightLink}>{ node }</a>;
}
return node;
}
}
interface IContent {
format?: string;
// eslint-disable-next-line camelcase
formatted_body?: string;
body: string;
}
@ -474,8 +450,13 @@ export function bodyToHtml(content: IContent, highlights: string[], opts: IOpts
});
return isDisplayedWithHtml ?
<span key="body" ref={opts.ref} className={className} dangerouslySetInnerHTML={{ __html: safeBody }} dir="auto" /> :
<span key="body" ref={opts.ref} className={className} dir="auto">{ strippedBody }</span>;
<span
key="body"
ref={opts.ref}
className={className}
dangerouslySetInnerHTML={{ __html: safeBody }}
dir="auto"
/> : <span key="body" ref={opts.ref} className={className} dir="auto">{ strippedBody }</span>;
}
/**

View file

@ -151,7 +151,7 @@ export class ModalManager {
prom: Promise<React.ComponentType>,
props?: IProps<T>,
className?: string,
options?: IOptions<T>
options?: IOptions<T>,
) {
const modal: IModal<T> = {
onFinished: props ? props.onFinished : null,
@ -182,7 +182,7 @@ export class ModalManager {
private getCloseFn<T extends any[]>(
modal: IModal<T>,
props: IProps<T>
props: IProps<T>,
): [IHandle<T>["close"], IHandle<T>["finished"]] {
const deferred = defer<T>();
return [async (...args: T) => {
@ -264,7 +264,7 @@ export class ModalManager {
className?: string,
isPriorityModal = false,
isStaticModal = false,
options: IOptions<T> = {}
options: IOptions<T> = {},
): IHandle<T> {
const {modal, closeDialog, onFinishedProm} = this.buildModal<T>(prom, props, className, options);
if (isPriorityModal) {
@ -287,7 +287,7 @@ export class ModalManager {
private appendDialogAsync<T extends any[]>(
prom: Promise<React.ComponentType>,
props?: IProps<T>,
className?: string
className?: string,
): IHandle<T> {
const {modal, closeDialog, onFinishedProm} = this.buildModal<T>(prom, props, className, {});

View file

@ -860,12 +860,12 @@ export const Commands = [
_t('WARNING: KEY VERIFICATION FAILED! The signing key for %(userId)s and session' +
' %(deviceId)s is "%(fprint)s" which does not match the provided key ' +
'"%(fingerprint)s". This could mean your communications are being intercepted!',
{
fprint,
userId,
deviceId,
fingerprint,
}));
{
fprint,
userId,
deviceId,
fingerprint,
}));
}
await cli.setDeviceVerified(userId, deviceId, true);
@ -879,7 +879,7 @@ export const Commands = [
{
_t('The signing key you provided matches the signing key you received ' +
'from %(userId)s\'s session %(deviceId)s. Session marked as verified.',
{userId, deviceId})
{userId, deviceId})
}
</p>
</div>,

View file

@ -168,7 +168,7 @@ const shortcuts: Record<Categories, IShortcut[]> = {
key: Key.U,
}],
description: _td("Upload a file"),
}
},
],
[Categories.ROOM_LIST]: [

View file

@ -190,7 +190,7 @@ export const RovingTabIndexProvider: React.FC<IProps> = ({children, handleHomeEn
ev.preventDefault();
ev.stopPropagation();
} else if (onKeyDown) {
return onKeyDown(ev, state);
return onKeyDown(ev, context.state);
}
}, [context.state, onKeyDown, handleHomeEnd]);

View file

@ -30,6 +30,7 @@ const Toolbar: React.FC<IProps> = ({children, ...props}) => {
const target = ev.target as HTMLElement;
let handled = true;
// HOME and END are handled by RovingTabIndexProvider
switch (ev.key) {
case Key.ARROW_UP:
case Key.ARROW_DOWN:
@ -47,8 +48,6 @@ const Toolbar: React.FC<IProps> = ({children, ...props}) => {
}
break;
// HOME and END are handled by RovingTabIndexProvider
default:
handled = false;
}

View file

@ -20,7 +20,7 @@ import React from "react";
import AccessibleTooltipButton from "../../components/views/elements/AccessibleTooltipButton";
interface IProps extends React.ComponentProps<typeof AccessibleTooltipButton> {
interface IProps extends React.ComponentProps<typeof AccessibleTooltipButton> {
// whether or not the context menu is currently open
isExpanded: boolean;
}

View file

@ -20,7 +20,8 @@ import AccessibleTooltipButton from "../../components/views/elements/AccessibleT
import {useRovingTabIndex} from "../RovingTabIndex";
import {Ref} from "./types";
interface IProps extends Omit<React.ComponentProps<typeof AccessibleTooltipButton>, "onFocus" | "inputRef" | "tabIndex"> {
type ATBProps = React.ComponentProps<typeof AccessibleTooltipButton>;
interface IProps extends Omit<ATBProps, "onFocus" | "inputRef" | "tabIndex"> {
inputRef?: Ref;
}

View file

@ -16,7 +16,6 @@ limitations under the License.
import React from "react";
import AccessibleButton from "../../components/views/elements/AccessibleButton";
import {useRovingTabIndex} from "../RovingTabIndex";
import {FocusHandler, Ref} from "./types";

View file

@ -89,7 +89,11 @@ export default class CommandProvider extends AutocompleteProvider {
renderCompletions(completions: React.ReactNode[]): React.ReactNode {
return (
<div className="mx_Autocomplete_Completion_container_block" role="listbox" aria-label={_t("Command Autocomplete")}>
<div
className="mx_Autocomplete_Completion_container_block"
role="listbox"
aria-label={_t("Command Autocomplete")}
>
{ completions }
</div>
);

View file

@ -91,15 +91,15 @@ export default class CommunityProvider extends AutocompleteProvider {
href: makeGroupPermalink(groupId),
component: (
<PillCompletion title={name} description={groupId}>
<BaseAvatar name={name || groupId}
width={24}
height={24}
url={avatarUrl ? cli.mxcUrlToHttp(avatarUrl, 24, 24) : null} />
<BaseAvatar
name={name || groupId}
width={24}
height={24}
url={avatarUrl ? cli.mxcUrlToHttp(avatarUrl, 24, 24) : null} />
</PillCompletion>
),
range,
}))
.slice(0, 4);
})).slice(0, 4);
}
return completions;
}

View file

@ -34,9 +34,9 @@ export const TextualCompletion = forwardRef<ITextualCompletionProps, any>((props
const {title, subtitle, description, className, ...restProps} = props;
return (
<div {...restProps}
className={classNames('mx_Autocomplete_Completion_block', className)}
role="option"
ref={ref}
className={classNames('mx_Autocomplete_Completion_block', className)}
role="option"
ref={ref}
>
<span className="mx_Autocomplete_Completion_title">{ title }</span>
<span className="mx_Autocomplete_Completion_subtitle">{ subtitle }</span>
@ -53,9 +53,9 @@ export const PillCompletion = forwardRef<IPillCompletionProps, any>((props, ref)
const {title, subtitle, description, className, children, ...restProps} = props;
return (
<div {...restProps}
className={classNames('mx_Autocomplete_Completion_pill', className)}
role="option"
ref={ref}
className={classNames('mx_Autocomplete_Completion_pill', className)}
role="option"
ref={ref}
>
{ children }
<span className="mx_Autocomplete_Completion_title">{ title }</span>

View file

@ -139,7 +139,11 @@ export default class EmojiProvider extends AutocompleteProvider {
renderCompletions(completions: React.ReactNode[]): React.ReactNode {
return (
<div className="mx_Autocomplete_Completion_container_pill" role="listbox" aria-label={_t("Emoji Autocomplete")}>
<div
className="mx_Autocomplete_Completion_container_pill"
role="listbox"
aria-label={_t("Emoji Autocomplete")}
>
{ completions }
</div>
);

View file

@ -110,9 +110,7 @@ export default class RoomProvider extends AutocompleteProvider {
),
range,
};
})
.filter((completion) => !!completion.completion && completion.completion.length > 0)
.slice(0, 4);
}).filter((completion) => !!completion.completion && completion.completion.length > 0).slice(0, 4);
}
return completions;
}

View file

@ -71,8 +71,13 @@ export default class UserProvider extends AutocompleteProvider {
}
}
private onRoomTimeline = (ev: MatrixEvent, room: Room, toStartOfTimeline: boolean, removed: boolean,
data: IRoomTimelineData) => {
private onRoomTimeline = (
ev: MatrixEvent,
room: Room,
toStartOfTimeline: boolean,
removed: boolean,
data: IRoomTimelineData,
) => {
if (!room) return;
if (removed) return;
if (room.roomId !== this.room.roomId) return;
@ -171,7 +176,11 @@ export default class UserProvider extends AutocompleteProvider {
renderCompletions(completions: React.ReactNode[]): React.ReactNode {
return (
<div className="mx_Autocomplete_Completion_container_pill" role="listbox" aria-label={_t("User Autocomplete")}>
<div
className="mx_Autocomplete_Completion_container_pill"
role="listbox"
aria-label={_t("User Autocomplete")}
>
{ completions }
</div>
);

View file

@ -233,8 +233,7 @@ export class ContextMenu extends React.PureComponent<IProps, IState> {
switch (ev.key) {
case Key.TAB:
case Key.ESCAPE:
// close on left and right arrows too for when it is a context menu on a <Toolbar />
case Key.ARROW_LEFT:
case Key.ARROW_LEFT: // close on left and right arrows too for when it is a context menu on a <Toolbar />
case Key.ARROW_RIGHT:
this.props.onFinished();
break;

View file

@ -377,7 +377,7 @@ export default class LeftPanel extends React.Component<IProps, IState> {
public render(): React.ReactNode {
const tagPanel = !this.state.showTagPanel ? null : (
<div className="mx_LeftPanel_tagPanelContainer">
<TagPanel/>
<TagPanel />
{SettingsStore.getValue("feature_custom_tags") ? <CustomRoomTagPanel /> : null}
</div>
);

View file

@ -43,11 +43,11 @@ import PlatformPeg from "../../PlatformPeg";
import { DefaultTagID } from "../../stores/room-list/models";
import {
showToast as showSetPasswordToast,
hideToast as hideSetPasswordToast
hideToast as hideSetPasswordToast,
} from "../../toasts/SetPasswordToast";
import {
showToast as showServerLimitToast,
hideToast as hideServerLimitToast
hideToast as hideServerLimitToast,
} from "../../toasts/ServerLimitToast";
import { Action } from "../../dispatcher/actions";
import LeftPanel from "./LeftPanel";
@ -79,6 +79,7 @@ interface IProps {
initialEventPixelOffset: number;
leftDisabled: boolean;
rightDisabled: boolean;
// eslint-disable-next-line camelcase
page_type: string;
autoJoin: boolean;
thirdPartyInvite?: object;
@ -98,7 +99,9 @@ interface IProps {
}
interface IUsageLimit {
// eslint-disable-next-line camelcase
limit_type: "monthly_active_user" | string;
// eslint-disable-next-line camelcase
admin_contact?: string;
}
@ -316,10 +319,10 @@ class LoggedInView extends React.Component<IProps, IState> {
}
};
_calculateServerLimitToast(syncErrorData: IState["syncErrorData"], usageLimitEventContent?: IUsageLimit) {
const error = syncErrorData && syncErrorData.error && syncErrorData.error.errcode === "M_RESOURCE_LIMIT_EXCEEDED";
_calculateServerLimitToast(syncError: IState["syncErrorData"], usageLimitEventContent?: IUsageLimit) {
const error = syncError && syncError.error && syncError.error.errcode === "M_RESOURCE_LIMIT_EXCEEDED";
if (error) {
usageLimitEventContent = syncErrorData.error.data;
usageLimitEventContent = syncError.error.data;
}
if (usageLimitEventContent) {
@ -620,18 +623,18 @@ class LoggedInView extends React.Component<IProps, IState> {
switch (this.props.page_type) {
case PageTypes.RoomView:
pageElement = <RoomView
ref={this._roomView}
autoJoin={this.props.autoJoin}
onRegistered={this.props.onRegistered}
thirdPartyInvite={this.props.thirdPartyInvite}
oobData={this.props.roomOobData}
viaServers={this.props.viaServers}
eventPixelOffset={this.props.initialEventPixelOffset}
key={this.props.currentRoomId || 'roomview'}
disabled={this.props.middleDisabled}
ConferenceHandler={this.props.ConferenceHandler}
resizeNotifier={this.props.resizeNotifier}
/>;
ref={this._roomView}
autoJoin={this.props.autoJoin}
onRegistered={this.props.onRegistered}
thirdPartyInvite={this.props.thirdPartyInvite}
oobData={this.props.roomOobData}
viaServers={this.props.viaServers}
eventPixelOffset={this.props.initialEventPixelOffset}
key={this.props.currentRoomId || 'roomview'}
disabled={this.props.middleDisabled}
ConferenceHandler={this.props.ConferenceHandler}
resizeNotifier={this.props.resizeNotifier}
/>;
break;
case PageTypes.MyGroups:

View file

@ -69,7 +69,7 @@ import { ViewUserPayload } from "../../dispatcher/payloads/ViewUserPayload";
import { Action } from "../../dispatcher/actions";
import {
showToast as showAnalyticsToast,
hideToast as hideAnalyticsToast
hideToast as hideAnalyticsToast,
} from "../../toasts/AnalyticsToast";
import {showToast as showNotificationsToast} from "../../toasts/DesktopNotificationsToast";
import { OpenToTabPayload } from "../../dispatcher/payloads/OpenToTabPayload";
@ -129,6 +129,7 @@ interface IScreen {
params?: object;
}
/* eslint-disable camelcase */
interface IRoomInfo {
room_id?: string;
room_alias?: string;
@ -140,6 +141,7 @@ interface IRoomInfo {
oob_data?: object;
via_servers?: string[];
}
/* eslint-enable camelcase */
interface IProps { // TODO type things better
config: Record<string, any>;
@ -165,6 +167,7 @@ interface IState {
// the master view we are showing.
view: Views;
// What the LoggedInView would be showing if visible
// eslint-disable-next-line camelcase
page_type?: PageTypes;
// The ID of the room we're viewing. This is either populated directly
// in the case where we view a room by ID or by RoomView when it resolves
@ -180,8 +183,11 @@ interface IState {
middleDisabled: boolean;
// the right panel's disabled state is tracked in its store.
// Parameters used in the registration dance with the IS
// eslint-disable-next-line camelcase
register_client_secret?: string;
// eslint-disable-next-line camelcase
register_session_id?: string;
// eslint-disable-next-line camelcase
register_id_sid?: string;
// When showing Modal dialogs we need to set aria-hidden on the root app element
// and disable it when there are no dialogs
@ -341,6 +347,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
}
// TODO: [REACT-WARNING] Replace with appropriate lifecycle stage
// eslint-disable-next-line camelcase
UNSAFE_componentWillUpdate(props, state) {
if (this.shouldTrackPageChange(this.state, state)) {
this.startPageChangeTimer();
@ -610,8 +617,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
const UserSettingsDialog = sdk.getComponent("dialogs.UserSettingsDialog");
Modal.createTrackedDialog('User settings', '', UserSettingsDialog,
{initialTabId: tabPayload.initialTabId},
/*className=*/null, /*isPriority=*/false, /*isStatic=*/true
);
/*className=*/null, /*isPriority=*/false, /*isStatic=*/true);
// View the welcome or home page if we need something to look at
this.viewSomethingBehindModal();
@ -1080,7 +1086,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
title: _t("Leave room"),
description: (
<span>
{ _t("Are you sure you want to leave the room '%(roomName)s'?", {roomName: roomToLeave.name}) }
{ _t("Are you sure you want to leave the room '%(roomName)s'?", {roomName: roomToLeave.name}) }
{ warnings }
</span>
),
@ -1433,7 +1439,6 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
cli.on("crypto.warning", (type) => {
switch (type) {
case 'CRYPTO_WARNING_OLD_VERSION_DETECTED':
const brand = SdkConfig.get().brand;
Modal.createTrackedDialog('Crypto migrated', '', ErrorDialog, {
title: _t('Old cryptography data detected'),
description: _t(
@ -1444,7 +1449,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
"in this version. This may also cause messages exchanged with this " +
"version to fail. If you experience problems, log out and back in " +
"again. To retain message history, export and re-import your keys.",
{ brand },
{ brand: SdkConfig.get().brand },
),
});
break;

View file

@ -20,7 +20,6 @@ import classNames from "classnames";
import defaultDispatcher from "../../dispatcher/dispatcher";
import { _t } from "../../languageHandler";
import { ActionPayload } from "../../dispatcher/payloads";
import { throttle } from 'lodash';
import { Key } from "../../Keyboard";
import AccessibleButton from "../views/elements/AccessibleButton";
import { Action } from "../../dispatcher/actions";
@ -137,7 +136,7 @@ export default class RoomSearch extends React.PureComponent<IProps, IState> {
});
let icon = (
<div className='mx_RoomSearch_icon'/>
<div className='mx_RoomSearch_icon' />
);
let input = (
<input

View file

@ -18,7 +18,6 @@ limitations under the License.
import * as React from "react";
import {_t} from '../../languageHandler';
import * as PropTypes from "prop-types";
import * as sdk from "../../index";
import AutoHideScrollbar from './AutoHideScrollbar';
import { ReactNode } from "react";

View file

@ -40,7 +40,7 @@ import AccessibleTooltipButton from "../views/elements/AccessibleTooltipButton";
import { SettingLevel } from "../../settings/SettingLevel";
import IconizedContextMenu, {
IconizedContextMenuOption,
IconizedContextMenuOptionList
IconizedContextMenuOptionList,
} from "../views/context_menus/IconizedContextMenu";
interface IProps {
@ -234,12 +234,12 @@ export default class UserMenu extends React.Component<IProps, IState> {
>
<div className="mx_UserMenu_contextMenu_header">
<div className="mx_UserMenu_contextMenu_name">
<span className="mx_UserMenu_contextMenu_displayName">
{OwnProfileStore.instance.displayName}
</span>
<span className="mx_UserMenu_contextMenu_displayName">
{OwnProfileStore.instance.displayName}
</span>
<span className="mx_UserMenu_contextMenu_userId">
{MatrixClientPeg.get().getUserId()}
</span>
{MatrixClientPeg.get().getUserId()}
</span>
</div>
<AccessibleTooltipButton
className="mx_UserMenu_contextMenu_themeButton"

View file

@ -17,7 +17,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React, {useCallback, useContext, useEffect, useMemo, useState} from 'react';
import React, {useCallback, useContext, useEffect, useState} from 'react';
import classNames from 'classnames';
import * as AvatarLogic from '../../../Avatar';
import SettingsStore from "../../../settings/SettingsStore";
@ -96,7 +96,7 @@ const BaseAvatar = (props: IProps) => {
urls,
width = 40,
height = 40,
resizeMethod = "crop", // eslint-disable-line no-unused-vars
resizeMethod = "crop", // eslint-disable-line @typescript-eslint/no-unused-vars
defaultToInitialLetter = true,
onClick,
inputRef,

View file

@ -126,7 +126,7 @@ export default class DecoratedRoomAvatar extends React.PureComponent<IProps, ISt
private onPresenceUpdate = () => {
if (this.isUnmounted) return;
let newIcon = this.getPresenceIcon();
const newIcon = this.getPresenceIcon();
if (newIcon !== this.state.icon) this.setState({icon: newIcon});
};

View file

@ -47,7 +47,7 @@ export default class GroupAvatar extends React.Component<IProps> {
render() {
// extract the props we use from props so we can pass any others through
// should consider adding this as a global rule in js-sdk?
/*eslint no-unused-vars: ["error", { "ignoreRestSiblings": true }]*/
/* eslint @typescript-eslint/no-unused-vars: ["error", { "ignoreRestSiblings": true }] */
const {groupId, groupAvatarUrl, groupName, ...otherProps} = this.props;
return (

View file

@ -25,4 +25,4 @@ const PulsedAvatar: React.FC<IProps> = (props) => {
</div>;
};
export default PulsedAvatar;
export default PulsedAvatar;

View file

@ -21,9 +21,6 @@ import { IDialogProps } from "./IDialogProps";
import Field from "../elements/Field";
import AccessibleButton from "../elements/AccessibleButton";
import { MatrixClientPeg } from "../../../MatrixClientPeg";
import InfoTooltip from "../elements/InfoTooltip";
import dis from "../../../dispatcher/dispatcher";
import {showCommunityRoomInviteDialog} from "../../../RoomInvite";
import { arrayFastClone } from "../../../utils/arrays";
import SdkConfig from "../../../SdkConfig";
import { RoomMember } from "matrix-js-sdk/src/models/room-member";
@ -31,7 +28,6 @@ import InviteDialog from "./InviteDialog";
import BaseAvatar from "../avatars/BaseAvatar";
import {getHttpUriForMxc} from "matrix-js-sdk/src/content-repo";
import {inviteMultipleToRoom, showAnyInviteErrors} from "../../../RoomInvite";
import {humanizeTime} from "../../../utils/humanize";
import StyledCheckbox from "../elements/StyledCheckbox";
import Modal from "../../../Modal";
import ErrorDialog from "./ErrorDialog";
@ -171,7 +167,7 @@ export default class CommunityPrototypeInviteDialog extends React.PureComponent<
public render() {
const emailAddresses = [];
this.state.emailTargets.forEach((address, i) => {
emailAddresses.push(
emailAddresses.push((
<Field
key={i}
value={address}
@ -180,11 +176,11 @@ export default class CommunityPrototypeInviteDialog extends React.PureComponent<
placeholder={_t("Email address")}
onBlur={() => this.onAddressBlur(i)}
/>
);
));
});
// Push a clean input
emailAddresses.push(
emailAddresses.push((
<Field
key={emailAddresses.length}
value={""}
@ -192,23 +188,23 @@ export default class CommunityPrototypeInviteDialog extends React.PureComponent<
label={emailAddresses.length > 0 ? _t("Add another email") : _t("Email address")}
placeholder={emailAddresses.length > 0 ? _t("Add another email") : _t("Email address")}
/>
);
));
let peopleIntro = null;
let people = [];
const people = [];
if (this.state.showPeople) {
const humansToPresent = this.state.people.slice(0, this.state.numPeople);
humansToPresent.forEach((person, i) => {
people.push(this.renderPerson(person, i));
});
if (humansToPresent.length < this.state.people.length) {
people.push(
people.push((
<AccessibleButton
onClick={this.onShowMorePeople}
kind="link" key="more"
className="mx_CommunityPrototypeInviteDialog_morePeople"
>{_t("Show more")}</AccessibleButton>
);
));
}
}
if (this.state.people.length > 0) {

View file

@ -163,8 +163,9 @@ export default class CreateCommunityPrototypeDialog extends React.PureComponent<
</span>
);
if (this.state.error) {
const classes = "mx_CreateCommunityPrototypeDialog_subtext mx_CreateCommunityPrototypeDialog_subtext_error";
helpText = (
<span className="mx_CreateCommunityPrototypeDialog_subtext mx_CreateCommunityPrototypeDialog_subtext_error">
<span className={classes}>
{this.state.error}
</span>
);
@ -205,7 +206,10 @@ export default class CreateCommunityPrototypeDialog extends React.PureComponent<
ref={this.avatarUploadRef} accept="image/*"
onChange={this.onAvatarChanged}
/>
<AccessibleButton onClick={this.onChangeAvatar} className="mx_CreateCommunityPrototypeDialog_avatarContainer">
<AccessibleButton
onClick={this.onChangeAvatar}
className="mx_CreateCommunityPrototypeDialog_avatarContainer"
>
{preview}
</AccessibleButton>
<div className="mx_CreateCommunityPrototypeDialog_tip">

View file

@ -186,8 +186,8 @@ export default class ShareDialog extends React.PureComponent<IProps, IState> {
title = _t('Share Room Message');
checkbox = <div>
<StyledCheckbox
checked={this.state.linkSpecificEvent}
onClick={this.onLinkSpecificEventCheckboxClick}
checked={this.state.linkSpecificEvent}
onClick={this.onLinkSpecificEventCheckboxClick}
>
{ _t('Link to selected message') }
</StyledCheckbox>
@ -198,16 +198,18 @@ export default class ShareDialog extends React.PureComponent<IProps, IState> {
const encodedUrl = encodeURIComponent(matrixToUrl);
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
return <BaseDialog title={title}
className='mx_ShareDialog'
contentId='mx_Dialog_content'
onFinished={this.props.onFinished}
return <BaseDialog
title={title}
className='mx_ShareDialog'
contentId='mx_Dialog_content'
onFinished={this.props.onFinished}
>
<div className="mx_ShareDialog_content">
<div className="mx_ShareDialog_matrixto">
<a href={matrixToUrl}
onClick={ShareDialog.onLinkClick}
className="mx_ShareDialog_matrixto_link"
<a
href={matrixToUrl}
onClick={ShareDialog.onLinkClick}
className="mx_ShareDialog_matrixto_link"
>
{ matrixToUrl }
</a>

View file

@ -34,7 +34,6 @@ export interface ILocationState {
}
export default class Draggable extends React.Component<IProps, IState> {
constructor(props: IProps) {
super(props);
@ -77,5 +76,4 @@ export default class Draggable extends React.Component<IProps, IState> {
render() {
return <div className={this.props.className} onMouseDown={this.onMouseDown.bind(this)} />;
}
}
}

View file

@ -39,11 +39,13 @@ interface IProps {
className: string;
}
/* eslint-disable camelcase */
interface IState {
userId: string;
displayname: string;
avatar_url: string;
}
/* eslint-enable camelcase */
const AVATAR_SIZE = 32;
@ -63,19 +65,18 @@ export default class EventTilePreview extends React.Component<IProps, IState> {
const client = MatrixClientPeg.get();
const userId = client.getUserId();
const profileInfo = await client.getProfileInfo(userId);
const avatar_url = Avatar.avatarUrlForUser(
const avatarUrl = Avatar.avatarUrlForUser(
{avatarUrl: profileInfo.avatar_url},
AVATAR_SIZE, AVATAR_SIZE, "crop");
this.setState({
userId,
displayname: profileInfo.displayname,
avatar_url,
avatar_url: avatarUrl,
});
}
private fakeEvent({userId, displayname, avatar_url}: IState) {
private fakeEvent({userId, displayname, avatar_url: avatarUrl}: IState) {
// Fake it till we make it
const event = new MatrixEvent(JSON.parse(`{
"type": "m.room.message",
@ -85,12 +86,12 @@ export default class EventTilePreview extends React.Component<IProps, IState> {
"msgtype": "m.text",
"body": "${this.props.message}",
"displayname": "${displayname}",
"avatar_url": "${avatar_url}"
"avatar_url": "${avatarUrl}"
},
"msgtype": "m.text",
"body": "${this.props.message}",
"displayname": "${displayname}",
"avatar_url": "${avatar_url}"
"avatar_url": "${avatarUrl}"
},
"unsigned": {
"age": 97
@ -104,7 +105,7 @@ export default class EventTilePreview extends React.Component<IProps, IState> {
name: displayname,
userId: userId,
getAvatarUrl: (..._) => {
return avatar_url;
return avatarUrl;
},
};
@ -114,13 +115,10 @@ export default class EventTilePreview extends React.Component<IProps, IState> {
public render() {
const event = this.fakeEvent(this.state);
let className = classnames(
this.props.className,
{
"mx_IRCLayout": this.props.useIRCLayout,
"mx_GroupLayout": !this.props.useIRCLayout,
}
);
const className = classnames(this.props.className, {
"mx_IRCLayout": this.props.useIRCLayout,
"mx_GroupLayout": !this.props.useIRCLayout,
});
return <div className={className}>
<EventTile mxEvent={event} useIRCLayout={this.props.useIRCLayout} />

View file

@ -198,11 +198,9 @@ export default class Field extends React.PureComponent<PropShapes, IState> {
}
}
public render() {
const {
element, prefixComponent, postfixComponent, className, onValidate, children,
/* eslint @typescript-eslint/no-unused-vars: ["error", { "ignoreRestSiblings": true }] */
const { element, prefixComponent, postfixComponent, className, onValidate, children,
tooltipContent, forceValidity, tooltipClassName, list, ...inputProps} = this.props;
// Set some defaults for the <input> element

View file

@ -78,7 +78,12 @@ export default class IRCTimelineProfileResizer extends React.Component<IProps, I
private onMoueUp(event: MouseEvent) {
if (this.props.roomId) {
SettingsStore.setValue("ircDisplayNameWidth", this.props.roomId, SettingLevel.ROOM_DEVICE, this.state.width);
SettingsStore.setValue(
"ircDisplayNameWidth",
this.props.roomId,
SettingLevel.ROOM_DEVICE,
this.state.width,
);
}
}

View file

@ -18,7 +18,6 @@ limitations under the License.
import React from 'react';
import classNames from 'classnames';
import AccessibleButton from "./AccessibleButton";
import Tooltip from './Tooltip';
import { _t } from "../../../languageHandler";

View file

@ -41,7 +41,7 @@ const QRCode: React.FC<IProps> = ({data, className, ...options}) => {
return () => {
cancelled = true;
};
}, [JSON.stringify(data), options]);
}, [JSON.stringify(data), options]); // eslint-disable-line react-hooks/exhaustive-deps
return <div className={classNames("mx_QRCode", className)}>
{ dataUri ? <img src={dataUri} className="mx_VerificationQRCode" alt={_t("QR Code")} /> : <Spinner /> }

View file

@ -45,7 +45,7 @@ export default class Slider extends React.Component<IProps> {
// non linear slider.
private offset(values: number[], value: number): number {
// the index of the first number greater than value.
let closest = values.reduce((prev, curr) => {
const closest = values.reduce((prev, curr) => {
return (value > curr ? prev + 1 : prev);
}, 0);
@ -68,17 +68,16 @@ export default class Slider extends React.Component<IProps> {
const linearInterpolation = (value - closestLessValue) / (closestGreaterValue - closestLessValue);
return 100 * (closest - 1 + linearInterpolation) * intervalWidth;
}
render(): React.ReactNode {
const dots = this.props.values.map(v =>
<Dot active={v <= this.props.value}
label={this.props.displayFunc(v)}
onClick={this.props.disabled ? () => {} : () => this.props.onSelectionChange(v)}
key={v}
disabled={this.props.disabled}
/>);
const dots = this.props.values.map(v => <Dot
active={v <= this.props.value}
label={this.props.displayFunc(v)}
onClick={this.props.disabled ? () => {} : () => this.props.onSelectionChange(v)}
key={v}
disabled={this.props.disabled}
/>);
let selection = null;
@ -93,7 +92,7 @@ export default class Slider extends React.Component<IProps> {
return <div className="mx_Slider">
<div>
<div className="mx_Slider_bar">
<hr onClick={this.props.disabled ? () => {} : this.onClick.bind(this)}/>
<hr onClick={this.props.disabled ? () => {} : this.onClick.bind(this)} />
{ selection }
</div>
<div className="mx_Slider_dotContainer">

View file

@ -17,8 +17,6 @@ limitations under the License.
import React from "react";
import { randomString } from "matrix-js-sdk/src/randomstring";
const CHECK_BOX_SVG = require("../../../../res/img/feather-customised/check.svg");
interface IProps extends React.InputHTMLAttributes<HTMLInputElement> {
}
@ -39,13 +37,14 @@ export default class StyledCheckbox extends React.PureComponent<IProps, IState>
}
public render() {
/* eslint @typescript-eslint/no-unused-vars: ["error", { "ignoreRestSiblings": true }] */
const { children, className, ...otherProps } = this.props;
return <span className={"mx_Checkbox " + className}>
<input id={this.id} {...otherProps} type="checkbox" />
<label htmlFor={this.id}>
{/* Using the div to center the image */}
<div className="mx_Checkbox_background">
<img src={CHECK_BOX_SVG}/>
<img src={require("../../../../res/img/feather-customised/check.svg")} />
</div>
<div>
{ this.props.children }
@ -53,4 +52,4 @@ export default class StyledCheckbox extends React.PureComponent<IProps, IState>
</label>
</span>;
}
}
}

View file

@ -76,14 +76,16 @@ const EncryptionInfo: React.FC<IProps> = ({
description = (
<div>
<p>{_t("Messages in this room are end-to-end encrypted.")}</p>
<p>{_t("Your messages are secured and only you and the recipient have the unique keys to unlock them.")}</p>
<p>{_t("Your messages are secured and only you and the recipient have " +
"the unique keys to unlock them.")}</p>
</div>
);
} else {
description = (
<div>
<p>{_t("Messages in this room are not end-to-end encrypted.")}</p>
<p>{_t("In encrypted rooms, your messages are secured and only you and the recipient have the unique keys to unlock them.")}</p>
<p>{_t("In encrypted rooms, your messages are secured and only you and the recipient have " +
"the unique keys to unlock them.")}</p>
</div>
);
}

View file

@ -23,7 +23,10 @@ import dis from '../../../dispatcher/dispatcher';
import RightPanelStore from "../../../stores/RightPanelStore";
import {RightPanelPhases} from "../../../stores/RightPanelStorePhases";
import {Action} from '../../../dispatcher/actions';
import {SetRightPanelPhasePayload, SetRightPanelPhaseRefireParams} from '../../../dispatcher/payloads/SetRightPanelPhasePayload';
import {
SetRightPanelPhasePayload,
SetRightPanelPhaseRefireParams,
} from '../../../dispatcher/payloads/SetRightPanelPhasePayload';
import {EventSubscription} from "fbemitter";
export enum HeaderKind {
@ -38,7 +41,7 @@ interface IState {
interface IProps {}
export default class HeaderButtons extends React.Component<IProps, IState> {
export default abstract class HeaderButtons extends React.Component<IProps, IState> {
private storeToken: EventSubscription;
private dispatcherRef: string;
@ -92,14 +95,7 @@ export default class HeaderButtons extends React.Component<IProps, IState> {
}
// XXX: Make renderButtons a prop
public renderButtons(): JSX.Element[] {
// Ignore - intended to be overridden by subclasses
// Return empty fragment to satisfy the type
return [
<React.Fragment>
</React.Fragment>
];
}
public abstract renderButtons(): JSX.Element[];
public render() {
// inline style as this will be swapped around in future commits

View file

@ -30,8 +30,6 @@ import {_t} from "../../../languageHandler";
import SdkConfig from "../../../SdkConfig";
import E2EIcon from "../rooms/E2EIcon";
import {
PHASE_UNSENT,
PHASE_REQUESTED,
PHASE_READY,
PHASE_DONE,
PHASE_STARTED,
@ -104,14 +102,15 @@ export default class VerificationPanel extends React.PureComponent<IProps, IStat
</div>;
}
if (showSAS) {
sasBlockDialog =
<div className='mx_VerificationPanel_QRPhase_startOption'>
<p>{_t("Compare unique emoji")}</p>
<span className='mx_VerificationPanel_QRPhase_helpText'>{_t("Compare a unique set of emoji if you don't have a camera on either device")}</span>
<AccessibleButton disabled={this.state.emojiButtonClicked} onClick={this.startSAS} kind='primary'>
{_t("Start")}
</AccessibleButton>
</div>;
sasBlockDialog = <div className='mx_VerificationPanel_QRPhase_startOption'>
<p>{_t("Compare unique emoji")}</p>
<span className='mx_VerificationPanel_QRPhase_helpText'>
{_t("Compare a unique set of emoji if you don't have a camera on either device")}
</span>
<AccessibleButton disabled={this.state.emojiButtonClicked} onClick={this.startSAS} kind='primary'>
{_t("Start")}
</AccessibleButton>
</div>;
}
const or = qrBlockDialog && sasBlockDialog ?
<div className='mx_VerificationPanel_QRPhase_betweenText'>{_t("or")}</div> : null;
@ -165,8 +164,8 @@ export default class VerificationPanel extends React.PureComponent<IProps, IStat
}
const noCommonMethodBlock = noCommonMethodError ?
<div className="mx_UserInfo_container">{noCommonMethodError}</div> :
null;
<div className="mx_UserInfo_container">{noCommonMethodError}</div> :
null;
// TODO: add way to open camera to scan a QR code
return <React.Fragment>

View file

@ -92,6 +92,7 @@ export default class NotificationBadge extends React.PureComponent<XOR<IProps, I
};
public render(): React.ReactElement {
/* eslint @typescript-eslint/no-unused-vars: ["error", { "ignoreRestSiblings": true }] */
const {notification, forceCount, roomId, onClick, ...props} = this.props;
// Don't show a badge if we don't need to

View file

@ -218,7 +218,7 @@ export default class RoomList extends React.PureComponent<IProps, IState> {
private getRoomDelta = (roomId: string, delta: number, unread = false) => {
const lists = RoomListStore.instance.orderedLists;
let rooms: Room = [];
const rooms: Room = [];
TAG_ORDER.forEach(t => {
let listRooms = lists[t];
@ -290,7 +290,7 @@ export default class RoomList extends React.PureComponent<IProps, IState> {
// TODO: Put community invites in a more sensible place (not in the room list)
// See https://github.com/vector-im/element-web/issues/14456
return MatrixClientPeg.get().getGroups().filter(g => {
return g.myMembership === 'invite';
return g.myMembership === 'invite';
}).map(g => {
const avatar = (
<GroupAvatar
@ -346,21 +346,19 @@ export default class RoomList extends React.PureComponent<IProps, IState> {
: TAG_AESTHETICS[orderedTagId];
if (!aesthetics) throw new Error(`Tag ${orderedTagId} does not have aesthetics`);
components.push(
<RoomSublist
key={`sublist-${orderedTagId}`}
tagId={orderedTagId}
forRooms={true}
startAsHidden={aesthetics.defaultHidden}
label={aesthetics.sectionLabelRaw ? aesthetics.sectionLabelRaw : _t(aesthetics.sectionLabel)}
onAddRoom={aesthetics.onAddRoom}
addRoomLabel={aesthetics.addRoomLabel ? _t(aesthetics.addRoomLabel) : aesthetics.addRoomLabel}
addRoomContextMenu={aesthetics.addRoomContextMenu}
isMinimized={this.props.isMinimized}
onResize={this.props.onResize}
extraBadTilesThatShouldntExist={extraTiles}
/>
);
components.push(<RoomSublist
key={`sublist-${orderedTagId}`}
tagId={orderedTagId}
forRooms={true}
startAsHidden={aesthetics.defaultHidden}
label={aesthetics.sectionLabelRaw ? aesthetics.sectionLabelRaw : _t(aesthetics.sectionLabel)}
onAddRoom={aesthetics.onAddRoom}
addRoomLabel={aesthetics.addRoomLabel ? _t(aesthetics.addRoomLabel) : aesthetics.addRoomLabel}
addRoomContextMenu={aesthetics.addRoomContextMenu}
isMinimized={this.props.isMinimized}
onResize={this.props.onResize}
extraBadTilesThatShouldntExist={extraTiles}
/>);
}
return components;

View file

@ -517,15 +517,13 @@ export default class RoomSublist extends React.Component<IProps, IState> {
if (this.state.rooms) {
const visibleRooms = this.state.rooms.slice(0, this.numVisibleTiles);
for (const room of visibleRooms) {
tiles.push(
<RoomTile
room={room}
key={`room-${room.roomId}`}
showMessagePreview={this.layout.showPreviews}
isMinimized={this.props.isMinimized}
tag={this.props.tagId}
/>
);
tiles.push(<RoomTile
room={room}
key={`room-${room.roomId}`}
showMessagePreview={this.layout.showPreviews}
isMinimized={this.props.isMinimized}
tag={this.props.tagId}
/>);
}
}
@ -710,7 +708,12 @@ export default class RoomSublist extends React.Component<IProps, IState> {
// doesn't become sticky.
// The same applies to the notification badge.
return (
<div className={classes} onKeyDown={this.onHeaderKeyDown} onFocus={onFocus} aria-label={this.props.label}>
<div
className={classes}
onKeyDown={this.onHeaderKeyDown}
onFocus={onFocus}
aria-label={this.props.label}
>
<div className="mx_RoomSublist_stickable">
<Button
onFocus={onFocus}
@ -762,7 +765,7 @@ export default class RoomSublist extends React.Component<IProps, IState> {
const showMoreAtMinHeight = minTiles < this.numTiles;
const minHeightPadding = RESIZE_HANDLE_HEIGHT + (showMoreAtMinHeight ? SHOW_N_BUTTON_HEIGHT : 0);
const minTilesPx = layout.tilesToPixelsWithPadding(minTiles, minHeightPadding);
let maxTilesPx = layout.tilesToPixelsWithPadding(this.numTiles, this.padding);
const maxTilesPx = layout.tilesToPixelsWithPadding(this.numTiles, this.padding);
const showMoreBtnClasses = classNames({
'mx_RoomSublist_showNButton': true,
});

View file

@ -31,7 +31,7 @@ import { ChevronFace, ContextMenuTooltipButton } from "../../structures/ContextM
import { DefaultTagID, TagID } from "../../../stores/room-list/models";
import { MessagePreviewStore, ROOM_PREVIEW_CHANGED } from "../../../stores/room-list/MessagePreviewStore";
import DecoratedRoomAvatar from "../avatars/DecoratedRoomAvatar";
import { ALL_MESSAGES, ALL_MESSAGES_LOUD, MENTIONS_ONLY, MUTE, } from "../../../RoomNotifs";
import { ALL_MESSAGES, ALL_MESSAGES_LOUD, MENTIONS_ONLY, MUTE } from "../../../RoomNotifs";
import { MatrixClientPeg } from "../../../MatrixClientPeg";
import NotificationBadge from "./NotificationBadge";
import { Volume } from "../../../RoomNotifsTypes";
@ -48,7 +48,7 @@ import IconizedContextMenu, {
IconizedContextMenuCheckbox,
IconizedContextMenuOption,
IconizedContextMenuOptionList,
IconizedContextMenuRadio
IconizedContextMenuRadio,
} from "../context_menus/IconizedContextMenu";
import { CommunityPrototypeStore, IRoomProfile } from "../../../stores/CommunityPrototypeStore";
import { UPDATE_EVENT } from "../../../stores/AsyncStore";
@ -249,7 +249,7 @@ export default class RoomTile extends React.PureComponent<IProps, IState> {
removeTag,
addTag,
undefined,
0
0,
));
} else {
console.warn(`Unexpected tag ${tagId} applied to ${this.props.room.room_id}`);

View file

@ -19,9 +19,7 @@ import classNames from "classnames";
import {
RovingAccessibleButton,
RovingAccessibleTooltipButton,
RovingTabIndexWrapper
} from "../../../accessibility/RovingTabIndex";
import AccessibleButton from "../../views/elements/AccessibleButton";
import NotificationBadge from "./NotificationBadge";
import { NotificationState } from "../../../stores/notifications/NotificationState";

View file

@ -42,7 +42,7 @@ function getStatusText(status: UpdateCheckStatus, errorDetail?: string) {
return _t('Downloading update...');
case UpdateCheckStatus.Ready:
return _t("New version available. <a>Update now.</a>", {}, {
a: sub => <AccessibleButton kind="link" onClick={installUpdate}>{sub}</AccessibleButton>
a: sub => <AccessibleButton kind="link" onClick={installUpdate}>{sub}</AccessibleButton>,
});
}
}

View file

@ -170,7 +170,7 @@ export default class AppearanceUserSettingsTab extends React.Component<IProps, I
"baseFontSize",
null,
SettingLevel.DEVICE,
parseInt(value, 10) - FontWatcher.SIZE_DIFF
parseInt(value, 10) - FontWatcher.SIZE_DIFF,
);
return {valid: true, feedback: _t('Use between %(min)s pt and %(max)s pt', {min, max})};
@ -294,7 +294,7 @@ export default class AppearanceUserSettingsTab extends React.Component<IProps, I
/>
</div>
{customThemeForm}
</div>
</div>
);
}

View file

@ -29,7 +29,15 @@ interface IProps extends IGenericToastProps {
const SECOND = 1000;
const GenericExpiringToast: React.FC<IProps> = ({description, acceptLabel, dismissLabel, onAccept, onDismiss, toastKey, numSeconds}) => {
const GenericExpiringToast: React.FC<IProps> = ({
description,
acceptLabel,
dismissLabel,
onAccept,
onDismiss,
toastKey,
numSeconds,
}) => {
const onReject = () => {
if (onDismiss) onDismiss();
ToastStore.sharedInstance().dismissToast(toastKey);

View file

@ -31,7 +31,13 @@ interface IPropsExtended extends IProps {
onReject();
}
const GenericToast: React.FC<XOR<IPropsExtended, IProps>> = ({description, acceptLabel, rejectLabel, onAccept, onReject}) => {
const GenericToast: React.FC<XOR<IPropsExtended, IProps>> = ({
description,
acceptLabel,
rejectLabel,
onAccept,
onReject,
}) => {
return <div>
<div className="mx_Toast_description">
{ description }

View file

@ -97,10 +97,7 @@ export default class CallView extends React.Component<IProps, IState> {
if (this.props.room) {
const roomId = this.props.room.roomId;
call = CallHandler.getCallForRoom(roomId) ||
(this.props.ConferenceHandler ?
this.props.ConferenceHandler.getConferenceCallForRoom(roomId) :
null
);
(this.props.ConferenceHandler ? this.props.ConferenceHandler.getConferenceCallForRoom(roomId) : null);
if (this.call) {
this.setState({ call: call });

View file

@ -51,7 +51,7 @@ export default class IncomingCallBox extends React.Component<IProps, IState> {
private onAction = (payload: ActionPayload) => {
switch (payload.action) {
case 'call_state':
case 'call_state': {
const call = CallHandler.getCall(payload.room_id);
if (call && call.call_state === 'ringing') {
this.setState({
@ -62,6 +62,7 @@ export default class IncomingCallBox extends React.Component<IProps, IState> {
incomingCall: null,
});
}
}
}
};

View file

@ -442,7 +442,7 @@ export function pickBestLanguage(langs: string[]): string {
}
function getLangsJson(): Promise<object> {
return new Promise(async (resolve, reject) => {
return new Promise((resolve, reject) => {
let url;
if (typeof(webpackLangJsonUrl) === 'string') { // in Jest this 'url' isn't a URL, so just fall through
url = webpackLangJsonUrl;
@ -453,7 +453,7 @@ function getLangsJson(): Promise<object> {
{ method: "GET", url },
(err, response, body) => {
if (err || response.status < 200 || response.status >= 300) {
reject({err: err, response: response});
reject(err);
return;
}
resolve(JSON.parse(body));
@ -488,7 +488,7 @@ function getLanguage(langPath: string): object {
{ method: "GET", url: langPath },
(err, response, body) => {
if (err || response.status < 200 || response.status >= 300) {
reject({err: err, response: response});
reject(err);
return;
}
resolve(weblateToCounterpart(JSON.parse(body)));