Merge remote-tracking branch 'upstream/develop' into compact-reply-rendering

This commit is contained in:
Tulir Asokan 2021-07-02 12:39:51 +03:00
commit 5240209529
782 changed files with 11943 additions and 10094 deletions

View file

@ -16,7 +16,7 @@
import React from 'react';
import {Key} from '../../../Keyboard';
import { Key } from '../../../Keyboard';
import classnames from 'classnames';
export type ButtonEvent = React.MouseEvent<Element> | React.KeyboardEvent<Element>;
@ -62,6 +62,8 @@ export default function AccessibleButton({
disabled,
inputRef,
className,
onKeyDown,
onKeyUp,
...restProps
}: IProps) {
const newProps: IAccessibleButtonProps = restProps;
@ -83,6 +85,8 @@ export default function AccessibleButton({
if (e.key === Key.SPACE) {
e.stopPropagation();
e.preventDefault();
} else {
onKeyDown?.(e);
}
};
newProps.onKeyUp = (e) => {
@ -94,6 +98,8 @@ export default function AccessibleButton({
if (e.key === Key.ENTER) {
e.stopPropagation();
e.preventDefault();
} else {
onKeyUp?.(e);
}
};
}

View file

@ -19,8 +19,8 @@ import React from 'react';
import classNames from 'classnames';
import AccessibleButton from "./AccessibleButton";
import Tooltip, {Alignment} from './Tooltip';
import {replaceableComponent} from "../../../utils/replaceableComponent";
import Tooltip, { Alignment } from './Tooltip';
import { replaceableComponent } from "../../../utils/replaceableComponent";
interface ITooltipProps extends React.ComponentProps<typeof AccessibleButton> {
title: string;
@ -67,7 +67,7 @@ export default class AccessibleTooltipButton extends React.PureComponent<IToolti
render() {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const {title, tooltip, children, tooltipClassName, forceHide, yOffset, alignment, ...props} = this.props;
const { title, tooltip, children, tooltipClassName, forceHide, yOffset, alignment, ...props } = this.props;
const tip = this.state.hover ? <Tooltip
className="mx_AccessibleTooltipButton_container"

View file

@ -20,7 +20,7 @@ import AccessibleButton from './AccessibleButton';
import dis from '../../../dispatcher/dispatcher';
import * as sdk from '../../../index';
import Analytics from '../../../Analytics';
import {replaceableComponent} from "../../../utils/replaceableComponent";
import { replaceableComponent } from "../../../utils/replaceableComponent";
@replaceableComponent("views.elements.ActionButton")
export default class ActionButton extends React.Component {
@ -47,23 +47,21 @@ export default class ActionButton extends React.Component {
_onClick = (ev) => {
ev.stopPropagation();
Analytics.trackEvent('Action Button', 'click', this.props.action);
dis.dispatch({action: this.props.action});
dis.dispatch({ action: this.props.action });
};
_onMouseEnter = () => {
if (this.props.tooltip) this.setState({showTooltip: true});
if (this.props.tooltip) this.setState({ showTooltip: true });
if (this.props.mouseOverAction) {
dis.dispatch({action: this.props.mouseOverAction});
dis.dispatch({ action: this.props.mouseOverAction });
}
};
_onMouseLeave = () => {
this.setState({showTooltip: false});
this.setState({ showTooltip: false });
};
render() {
const TintableSvg = sdk.getComponent("elements.TintableSvg");
let tooltip;
if (this.state.showTooltip) {
const Tooltip = sdk.getComponent("elements.Tooltip");
@ -71,7 +69,7 @@ export default class ActionButton extends React.Component {
}
const icon = this.props.iconPath ?
(<TintableSvg src={this.props.iconPath} width={this.props.size} height={this.props.size} />) :
(<img src={this.props.iconPath} width={this.props.size} height={this.props.size} />) :
undefined;
const classNames = ["mx_RoleButton"];

View file

@ -20,7 +20,7 @@ import PropTypes from 'prop-types';
import * as sdk from '../../../index';
import classNames from 'classnames';
import { UserAddressType } from '../../../UserAddress';
import {replaceableComponent} from "../../../utils/replaceableComponent";
import { replaceableComponent } from "../../../utils/replaceableComponent";
@replaceableComponent("views.elements.AddressSelector")
export default class AddressSelector extends React.Component {

View file

@ -20,9 +20,9 @@ import PropTypes from 'prop-types';
import classNames from 'classnames';
import * as sdk from "../../../index";
import { _t } from '../../../languageHandler';
import { UserAddressType } from '../../../UserAddress.js';
import {replaceableComponent} from "../../../utils/replaceableComponent";
import {mediaFromMxc} from "../../../customisations/Media";
import { UserAddressType } from '../../../UserAddress';
import { replaceableComponent } from "../../../utils/replaceableComponent";
import { mediaFromMxc } from "../../../customisations/Media";
@replaceableComponent("views.elements.AddressTile")
export default class AddressTile extends React.Component {
@ -53,7 +53,6 @@ export default class AddressTile extends React.Component {
}
const BaseAvatar = sdk.getComponent('avatars.BaseAvatar');
const TintableSvg = sdk.getComponent("elements.TintableSvg");
const nameClasses = classNames({
"mx_AddressTile_name": true,
@ -124,7 +123,7 @@ export default class AddressTile extends React.Component {
if (this.props.canDismiss) {
dismiss = (
<div className="mx_AddressTile_dismiss" onClick={this.props.onDismissed} >
<TintableSvg src={require("../../../../res/img/icon-address-delete.svg")} width="9" height="9" />
<img src={require("../../../../res/img/icon-address-delete.svg")} width="9" height="9" />
</div>
);
}

View file

@ -23,8 +23,8 @@ import * as sdk from '../../../index';
import { _t } from '../../../languageHandler';
import SdkConfig from '../../../SdkConfig';
import WidgetUtils from "../../../utils/WidgetUtils";
import {MatrixClientPeg} from "../../../MatrixClientPeg";
import {replaceableComponent} from "../../../utils/replaceableComponent";
import { MatrixClientPeg } from "../../../MatrixClientPeg";
import { replaceableComponent } from "../../../utils/replaceableComponent";
@replaceableComponent("views.elements.AppPermission")
export default class AppPermission extends React.Component {
@ -115,9 +115,9 @@ export default class AppPermission extends React.Component {
// Due to i18n limitations, we can't dedupe the code for variables in these two messages.
const warning = this.state.isWrapped
? _t("Using this widget may share data <helpIcon /> with %(widgetDomain)s & your Integration Manager.",
{widgetDomain: this.state.widgetDomain}, {helpIcon: () => warningTooltip})
{ widgetDomain: this.state.widgetDomain }, { helpIcon: () => warningTooltip })
: _t("Using this widget may share data <helpIcon /> with %(widgetDomain)s.",
{widgetDomain: this.state.widgetDomain}, {helpIcon: () => warningTooltip});
{ widgetDomain: this.state.widgetDomain }, { helpIcon: () => warningTooltip });
const encryptionWarning = this.props.isRoomEncrypted ? _t("Widgets do not use message encryption.") : null;

View file

@ -18,9 +18,9 @@ limitations under the License.
*/
import url from 'url';
import React, {createRef} from 'react';
import React, { createRef } from 'react';
import PropTypes from 'prop-types';
import {MatrixClientPeg} from '../../../MatrixClientPeg';
import { MatrixClientPeg } from '../../../MatrixClientPeg';
import AccessibleButton from './AccessibleButton';
import { _t } from '../../../languageHandler';
import AppPermission from './AppPermission';
@ -30,15 +30,15 @@ import dis from '../../../dispatcher/dispatcher';
import ActiveWidgetStore from '../../../stores/ActiveWidgetStore';
import classNames from 'classnames';
import SettingsStore from "../../../settings/SettingsStore";
import {aboveLeftOf, ContextMenuButton} from "../../structures/ContextMenu";
import PersistedElement, {getPersistKey} from "./PersistedElement";
import {WidgetType} from "../../../widgets/WidgetType";
import {StopGapWidget} from "../../../stores/widgets/StopGapWidget";
import {ElementWidgetActions} from "../../../stores/widgets/ElementWidgetActions";
import {MatrixCapabilities} from "matrix-widget-api";
import { aboveLeftOf, ContextMenuButton } from "../../structures/ContextMenu";
import PersistedElement, { getPersistKey } from "./PersistedElement";
import { WidgetType } from "../../../widgets/WidgetType";
import { StopGapWidget } from "../../../stores/widgets/StopGapWidget";
import { ElementWidgetActions } from "../../../stores/widgets/ElementWidgetActions";
import { MatrixCapabilities } from "matrix-widget-api";
import RoomWidgetContextMenu from "../context_menus/WidgetContextMenu";
import WidgetAvatar from "../avatars/WidgetAvatar";
import {replaceableComponent} from "../../../utils/replaceableComponent";
import { replaceableComponent } from "../../../utils/replaceableComponent";
@replaceableComponent("views.elements.AppTile")
export default class AppTile extends React.Component {
@ -164,7 +164,7 @@ export default class AppTile extends React.Component {
_startWidget() {
this._sgWidget.prepare().then(() => {
this.setState({initialising: false});
this.setState({ initialising: false });
});
}
@ -213,17 +213,17 @@ export default class AppTile extends React.Component {
}
if (WidgetType.JITSI.matches(this.props.app.type)) {
dis.dispatch({action: 'hangup_conference'});
dis.dispatch({ action: 'hangup_conference' });
}
// Delete the widget from the persisted store for good measure.
PersistedElement.destroyElement(this._persistKey);
if (this._sgWidget) this._sgWidget.stop({forceDestroy: true});
if (this._sgWidget) this._sgWidget.stop({ forceDestroy: true });
}
_onWidgetPrepared = () => {
this.setState({loading: false});
this.setState({ loading: false });
};
_onWidgetReady = () => {
@ -237,7 +237,7 @@ export default class AppTile extends React.Component {
switch (payload.action) {
case 'm.sticker':
if (this._sgWidget.widgetApi.hasCapability(MatrixCapabilities.StickerSending)) {
dis.dispatch({action: 'post_sticker_message', data: payload.data});
dis.dispatch({ action: 'post_sticker_message', data: payload.data });
} else {
console.warn('Ignoring sticker message. Invalid capability');
}
@ -253,7 +253,7 @@ export default class AppTile extends React.Component {
current[this.props.app.eventId] = true;
const level = SettingsStore.firstSupportedLevel("allowedWidgets");
SettingsStore.setValue("allowedWidgets", roomId, level, current).then(() => {
this.setState({hasPermissionToLoad: true});
this.setState({ hasPermissionToLoad: true });
// Fetch a token for the integration manager, now that we're allowed to
this._startWidget();
@ -313,7 +313,7 @@ export default class AppTile extends React.Component {
// Using Object.assign workaround as the following opens in a new window instead of a new tab.
// window.open(this._getPopoutUrl(), '_blank', 'noopener=yes');
Object.assign(document.createElement('a'),
{ target: '_blank', href: this._sgWidget.popoutUrl, rel: 'noreferrer noopener'}).click();
{ target: '_blank', href: this._sgWidget.popoutUrl, rel: 'noreferrer noopener' }).click();
};
_onContextMenuClick = () => {
@ -416,11 +416,11 @@ export default class AppTile extends React.Component {
let appTileClasses;
if (this.props.miniMode) {
appTileClasses = {mx_AppTile_mini: true};
appTileClasses = { mx_AppTile_mini: true };
} else if (this.props.fullWidth) {
appTileClasses = {mx_AppTileFullWidth: true};
appTileClasses = { mx_AppTileFullWidth: true };
} else {
appTileClasses = {mx_AppTile: true};
appTileClasses = { mx_AppTile: true };
}
appTileClasses = classNames(appTileClasses);
@ -443,7 +443,7 @@ export default class AppTile extends React.Component {
<div className={appTileClasses} id={this.props.app.id}>
{ this.props.showMenubar &&
<div className="mx_AppTileMenuBar">
<span className="mx_AppTileMenuBarTitle" style={{pointerEvents: (this.props.handleMinimisePointerEvents ? 'all' : false)}}>
<span className="mx_AppTileMenuBarTitle" style={{ pointerEvents: (this.props.handleMinimisePointerEvents ? 'all' : false) }}>
{ this.props.showTitle && this._getTileTitle() }
</span>
<span className="mx_AppTileMenuBarWidgets">

View file

@ -0,0 +1,56 @@
/*
Copyright 2020 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import { decode } from "blurhash";
interface IProps {
blurhash: string;
width: number;
height: number;
}
export default class BlurhashPlaceholder extends React.PureComponent<IProps> {
private canvas: React.RefObject<HTMLCanvasElement> = React.createRef();
public componentDidMount() {
this.draw();
}
public componentDidUpdate() {
this.draw();
}
private draw() {
if (!this.canvas.current) return;
try {
const { width, height } = this.props;
const pixels = decode(this.props.blurhash, Math.ceil(width), Math.ceil(height));
const ctx = this.canvas.current.getContext("2d");
const imgData = ctx.createImageData(width, height);
imgData.data.set(pixels);
ctx.putImageData(imgData, 0, 0);
} catch (e) {
console.error("Error rendering blurhash: ", e);
}
}
public render() {
return <canvas height={this.props.height} width={this.props.width} ref={this.canvas} />;
}
}

View file

@ -18,7 +18,6 @@ limitations under the License.
import TagTile from './TagTile';
import React from 'react';
import { Draggable } from 'react-beautiful-dnd';
import { ContextMenu, toRightOf, useContextMenu } from "../../structures/ContextMenu";
import * as sdk from '../../../index';
@ -31,32 +30,17 @@ export default function DNDTagTile(props) {
const TagTileContextMenu = sdk.getComponent('context_menus.TagTileContextMenu');
contextMenu = (
<ContextMenu {...toRightOf(elementRect)} onFinished={closeMenu}>
<TagTileContextMenu tag={props.tag} onFinished={closeMenu} />
<TagTileContextMenu tag={props.tag} onFinished={closeMenu} index={props.index} />
</ContextMenu>
);
}
return <div>
<Draggable
key={props.tag}
draggableId={props.tag}
index={props.index}
type="draggable-TagTile"
>
{(provided, snapshot) => (
<div
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
>
<TagTile
{...props}
contextMenuButtonRef={handle}
menuDisplayed={menuDisplayed}
openMenu={openMenu}
/>
</div>
)}
</Draggable>
return <>
<TagTile
{...props}
contextMenuButtonRef={handle}
menuDisplayed={menuDisplayed}
openMenu={openMenu}
/>
{contextMenu}
</div>;
</>;
}

View file

@ -14,10 +14,13 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React from "react";
import EventIndexPeg from "../../../indexing/EventIndexPeg";
import { _t } from "../../../languageHandler";
import SdkConfig from "../../../SdkConfig";
import React from "react";
import dis from "../../../dispatcher/dispatcher";
import { Action } from "../../../dispatcher/actions";
import { UserTab } from "../dialogs/UserSettingsDialog";
export enum WarningKind {
Files,
@ -29,11 +32,27 @@ interface IProps {
kind: WarningKind;
}
export default function DesktopBuildsNotice({isRoomEncrypted, kind}: IProps) {
export default function DesktopBuildsNotice({ isRoomEncrypted, kind }: IProps) {
if (!isRoomEncrypted) return null;
if (EventIndexPeg.get()) return null;
const {desktopBuilds, brand} = SdkConfig.get();
if (EventIndexPeg.error) {
return <>
{_t("Message search initialisation failed, check <a>your settings</a> for more information", {}, {
a: sub => (<a onClick={(evt) => {
evt.preventDefault();
dis.dispatch({
action: Action.ViewUserSettings,
initialTabId: UserTab.Security,
});
}}>
{sub}
</a>),
})}
</>;
}
const { desktopBuilds, brand } = SdkConfig.get();
let text = null;
let logo = null;
@ -54,10 +73,10 @@ export default function DesktopBuildsNotice({isRoomEncrypted, kind}: IProps) {
} else {
switch (kind) {
case WarningKind.Files:
text = _t("This version of %(brand)s does not support viewing some encrypted files", {brand});
text = _t("This version of %(brand)s does not support viewing some encrypted files", { brand });
break;
case WarningKind.Search:
text = _t("This version of %(brand)s does not support searching encrypted messages", {brand});
text = _t("This version of %(brand)s does not support searching encrypted messages", { brand });
break;
}
}

View file

@ -16,10 +16,10 @@ limitations under the License.
import React from 'react';
import { _t } from '../../../languageHandler';
import BaseDialog from "..//dialogs/BaseDialog"
import BaseDialog from "..//dialogs/BaseDialog";
import AccessibleButton from './AccessibleButton';
import {getDesktopCapturerSources} from "matrix-js-sdk/src/webrtc/call";
import {replaceableComponent} from "../../../utils/replaceableComponent";
import { getDesktopCapturerSources } from "matrix-js-sdk/src/webrtc/call";
import { replaceableComponent } from "../../../utils/replaceableComponent";
export interface DesktopCapturerSource {
id: string;
@ -44,7 +44,7 @@ export class ExistingSource extends React.Component<DesktopCapturerSourceIProps>
onClick = (ev) => {
this.props.onSelect(this.props.source);
}
};
render() {
return (
@ -108,19 +108,19 @@ export default class DesktopCapturerSourcePicker extends React.Component<
onSelect = (source) => {
this.props.onFinished(source);
}
};
onScreensClick = (ev) => {
this.setState({selectedTab: Tabs.Screens});
}
this.setState({ selectedTab: Tabs.Screens });
};
onWindowsClick = (ev) => {
this.setState({selectedTab: Tabs.Windows});
}
this.setState({ selectedTab: Tabs.Windows });
};
onCloseClick = (ev) => {
this.props.onFinished(null);
}
};
render() {
let sources;

View file

@ -19,7 +19,7 @@ limitations under the License.
import React from "react";
import PropTypes from "prop-types";
import { _t } from '../../../languageHandler';
import {replaceableComponent} from "../../../utils/replaceableComponent";
import { replaceableComponent } from "../../../utils/replaceableComponent";
/**
* Basic container for buttons in modal dialogs.

View file

@ -18,7 +18,7 @@ import React from 'react';
import PropTypes from 'prop-types';
import * as sdk from '../../../index';
import { _t } from '../../../languageHandler';
import {replaceableComponent} from "../../../utils/replaceableComponent";
import { replaceableComponent } from "../../../utils/replaceableComponent";
@replaceableComponent("views.elements.DirectorySearchBox")
export default class DirectorySearchBox extends React.Component {
@ -42,7 +42,7 @@ export default class DirectorySearchBox extends React.Component {
}
_onClearClick() {
this.setState({value: ''});
this.setState({ value: '' });
if (this.input) {
this.input.focus();
@ -55,7 +55,7 @@ export default class DirectorySearchBox extends React.Component {
_onChange(ev) {
if (!this.input) return;
this.setState({value: ev.target.value});
this.setState({ value: ev.target.value });
if (this.props.onChange) {
this.props.onChange(ev.target.value);

View file

@ -15,7 +15,7 @@ limitations under the License.
*/
import React from 'react';
import {replaceableComponent} from "../../../utils/replaceableComponent";
import { replaceableComponent } from "../../../utils/replaceableComponent";
interface IProps {
className: string;

View file

@ -16,13 +16,13 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React, {createRef} from 'react';
import React, { createRef } from 'react';
import PropTypes from 'prop-types';
import classnames from 'classnames';
import AccessibleButton from './AccessibleButton';
import { _t } from '../../../languageHandler';
import {Key} from "../../../Keyboard";
import {replaceableComponent} from "../../../utils/replaceableComponent";
import { Key } from "../../../Keyboard";
import { replaceableComponent } from "../../../utils/replaceableComponent";
class MenuOption extends React.Component {
constructor(props) {

View file

@ -1,5 +1,5 @@
/*
Copyright 2017, 2019 New Vector Ltd.
Copyright 2017-2021 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -14,48 +14,48 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import PropTypes from 'prop-types';
import {_t} from '../../../languageHandler';
import React from "react";
import { _t } from '../../../languageHandler';
import Field from "./Field";
import AccessibleButton from "./AccessibleButton";
import {replaceableComponent} from "../../../utils/replaceableComponent";
import { replaceableComponent } from "../../../utils/replaceableComponent";
export class EditableItem extends React.Component {
static propTypes = {
index: PropTypes.number,
value: PropTypes.string,
onRemove: PropTypes.func,
interface IItemProps {
index?: number;
value?: string;
onRemove?(index: number): void;
}
interface IItemState {
verifyRemove: boolean;
}
export class EditableItem extends React.Component<IItemProps, IItemState> {
public state = {
verifyRemove: false,
};
constructor() {
super();
this.state = {
verifyRemove: false,
};
}
_onRemove = (e) => {
private onRemove = (e) => {
e.stopPropagation();
e.preventDefault();
this.setState({verifyRemove: true});
this.setState({ verifyRemove: true });
};
_onDontRemove = (e) => {
private onDontRemove = (e) => {
e.stopPropagation();
e.preventDefault();
this.setState({verifyRemove: false});
this.setState({ verifyRemove: false });
};
_onActuallyRemove = (e) => {
private onActuallyRemove = (e) => {
e.stopPropagation();
e.preventDefault();
if (this.props.onRemove) this.props.onRemove(this.props.index);
this.setState({verifyRemove: false});
this.setState({ verifyRemove: false });
};
render() {
@ -66,14 +66,14 @@ export class EditableItem extends React.Component {
{_t("Are you sure?")}
</span>
<AccessibleButton
onClick={this._onActuallyRemove}
onClick={this.onActuallyRemove}
kind="primary_sm"
className="mx_EditableItem_confirmBtn"
>
{_t("Yes")}
</AccessibleButton>
<AccessibleButton
onClick={this._onDontRemove}
onClick={this.onDontRemove}
kind="danger_sm"
className="mx_EditableItem_confirmBtn"
>
@ -85,59 +85,68 @@ export class EditableItem extends React.Component {
return (
<div className="mx_EditableItem">
<div onClick={this._onRemove} className="mx_EditableItem_delete" title={_t("Remove")} role="button" />
<div onClick={this.onRemove} className="mx_EditableItem_delete" title={_t("Remove")} role="button" />
<span className="mx_EditableItem_item">{this.props.value}</span>
</div>
);
}
}
interface IProps {
id: string;
items: string[];
itemsLabel?: string;
noItemsLabel?: string;
placeholder?: string;
newItem?: string;
canEdit?: boolean;
canRemove?: boolean;
suggestionsListId?: string;
onItemAdded?(item: string): void;
onItemRemoved?(index: number): void;
onNewItemChanged?(item: string): void;
}
@replaceableComponent("views.elements.EditableItemList")
export default class EditableItemList extends React.Component {
static propTypes = {
id: PropTypes.string.isRequired,
items: PropTypes.arrayOf(PropTypes.string).isRequired,
itemsLabel: PropTypes.string,
noItemsLabel: PropTypes.string,
placeholder: PropTypes.string,
newItem: PropTypes.string,
onItemAdded: PropTypes.func,
onItemRemoved: PropTypes.func,
onNewItemChanged: PropTypes.func,
canEdit: PropTypes.bool,
canRemove: PropTypes.bool,
};
_onItemAdded = (e) => {
export default class EditableItemList<P = {}> extends React.PureComponent<IProps & P> {
protected onItemAdded = (e) => {
e.stopPropagation();
e.preventDefault();
if (this.props.onItemAdded) this.props.onItemAdded(this.props.newItem);
};
_onItemRemoved = (index) => {
protected onItemRemoved = (index) => {
if (this.props.onItemRemoved) this.props.onItemRemoved(index);
};
_onNewItemChanged = (e) => {
protected onNewItemChanged = (e) => {
if (this.props.onNewItemChanged) this.props.onNewItemChanged(e.target.value);
};
_renderNewItemField() {
protected renderNewItemField() {
return (
<form
onSubmit={this._onItemAdded}
onSubmit={this.onItemAdded}
autoComplete="off"
noValidate={true}
className="mx_EditableItemList_newItem"
>
<Field label={this.props.placeholder} type="text"
autoComplete="off" value={this.props.newItem || ""} onChange={this._onNewItemChanged}
list={this.props.suggestionsListId} />
<AccessibleButton onClick={this._onItemAdded} kind="primary" type="submit" disabled={!this.props.newItem}>
{_t("Add")}
<Field
label={this.props.placeholder}
type="text"
autoComplete="off"
value={this.props.newItem || ""}
onChange={this.onNewItemChanged}
list={this.props.suggestionsListId}
/>
<AccessibleButton
onClick={this.onItemAdded}
kind="primary"
type="submit"
disabled={!this.props.newItem}
>
{ _t("Add") }
</AccessibleButton>
</form>
);
@ -153,19 +162,21 @@ export default class EditableItemList extends React.Component {
key={item}
index={index}
value={item}
onRemove={this._onItemRemoved}
onRemove={this.onItemRemoved}
/>;
});
const editableItemsSection = this.props.canRemove ? editableItems : <ul>{editableItems}</ul>;
const label = this.props.items.length > 0 ? this.props.itemsLabel : this.props.noItemsLabel;
return (<div className="mx_EditableItemList">
<div className="mx_EditableItemList_label">
{ label }
return (
<div className="mx_EditableItemList">
<div className="mx_EditableItemList_label">
{ label }
</div>
{ editableItemsSection }
{ this.props.canEdit ? this.renderNewItemField() : <div /> }
</div>
{ editableItemsSection }
{ this.props.canEdit ? this._renderNewItemField() : <div /> }
</div>);
);
}
}

View file

@ -15,10 +15,10 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React, {createRef} from 'react';
import React, { createRef } from 'react';
import PropTypes from 'prop-types';
import {Key} from "../../../Keyboard";
import {replaceableComponent} from "../../../utils/replaceableComponent";
import { Key } from "../../../Keyboard";
import { replaceableComponent } from "../../../utils/replaceableComponent";
@replaceableComponent("views.elements.EditableText")
export default class EditableText extends React.Component {
@ -209,7 +209,7 @@ export default class EditableText extends React.Component {
};
render() {
const {className, editable, initialValue, label, labelClassName} = this.props;
const { className, editable, initialValue, label, labelClassName } = this.props;
let editableEl;
if (!editable || (this.state.phase === EditableText.Phases.Display &&

View file

@ -17,7 +17,7 @@ limitations under the License.
import React from 'react';
import PropTypes from 'prop-types';
import * as sdk from '../../../index';
import {replaceableComponent} from "../../../utils/replaceableComponent";
import { replaceableComponent } from "../../../utils/replaceableComponent";
/**
* A component which wraps an EditableText, with a spinner while updates take
@ -50,7 +50,7 @@ export default class EditableTextContainer extends React.Component {
return;
}
this.setState({busy: true});
this.setState({ busy: true });
this.props.getInitialValue().then(
(result) => {
@ -144,7 +144,6 @@ EditableTextContainer.propTypes = {
blurToSubmit: PropTypes.bool,
};
EditableTextContainer.defaultProps = {
initialValue: "",
placeholder: "",

View file

@ -17,7 +17,7 @@
import React, { FunctionComponent, useEffect, useRef } from 'react';
import dis from '../../../dispatcher/dispatcher';
import ICanvasEffect from '../../../effects/ICanvasEffect';
import { CHAT_EFFECTS } from '../../../effects'
import { CHAT_EFFECTS } from '../../../effects';
import UIStore, { UI_EVENTS } from "../../../stores/UIStore";
interface IProps {
@ -32,7 +32,7 @@ const EffectsOverlay: FunctionComponent<IProps> = ({ roomWidth }) => {
if (!name) return null;
let effect: ICanvasEffect | null = effectsRef.current[name] || null;
if (effect === null) {
const options = CHAT_EFFECTS.find((e) => e.command === name)?.options
const options = CHAT_EFFECTS.find((e) => e.command === name)?.options;
try {
const { default: Effect } = await import(`../../../effects/${name}`);
effect = new Effect(options);
@ -56,7 +56,7 @@ const EffectsOverlay: FunctionComponent<IProps> = ({ roomWidth }) => {
const effect = payload.action.substr(actionPrefix.length);
lazyLoadEffectModule(effect).then((module) => module?.start(canvasRef.current));
}
}
};
const dispatcherRef = dis.register(onAction);
const canvas = canvasRef.current;
canvas.height = UIStore.instance.windowHeight;
@ -89,7 +89,7 @@ const EffectsOverlay: FunctionComponent<IProps> = ({ roomWidth }) => {
right: 0,
}}
/>
)
}
);
};
export default EffectsOverlay;

View file

@ -1,5 +1,5 @@
/*
Copyright 2019 The Matrix.org Foundation C.I.C.
Copyright 2019 - 2021 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -14,21 +14,27 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import * as sdk from '../../../index';
import React, { ErrorInfo } from 'react';
import { _t } from '../../../languageHandler';
import {MatrixClientPeg} from '../../../MatrixClientPeg';
import { MatrixClientPeg } from '../../../MatrixClientPeg';
import PlatformPeg from '../../../PlatformPeg';
import Modal from '../../../Modal';
import SdkConfig from "../../../SdkConfig";
import {replaceableComponent} from "../../../utils/replaceableComponent";
import { replaceableComponent } from "../../../utils/replaceableComponent";
import BugReportDialog from '../dialogs/BugReportDialog';
import AccessibleButton from './AccessibleButton';
interface IState {
error: Error;
}
/**
* This error boundary component can be used to wrap large content areas and
* catch exceptions during rendering in the component tree below them.
*/
@replaceableComponent("views.elements.ErrorBoundary")
export default class ErrorBoundary extends React.PureComponent {
export default class ErrorBoundary extends React.PureComponent<{}, IState> {
constructor(props) {
super(props);
@ -37,13 +43,13 @@ export default class ErrorBoundary extends React.PureComponent {
};
}
static getDerivedStateFromError(error) {
static getDerivedStateFromError(error: Error): Partial<IState> {
// Side effects are not permitted here, so we only update the state so
// that the next render shows an error message.
return { error };
}
componentDidCatch(error, { componentStack }) {
componentDidCatch(error: Error, { componentStack }: ErrorInfo): void {
// Browser consoles are better at formatting output when native errors are passed
// in their own `console.error` invocation.
console.error(error);
@ -53,7 +59,7 @@ export default class ErrorBoundary extends React.PureComponent {
);
}
_onClearCacheAndReload = () => {
private onClearCacheAndReload = (): void => {
if (!PlatformPeg.get()) return;
MatrixClientPeg.get().stopClient();
@ -62,11 +68,7 @@ export default class ErrorBoundary extends React.PureComponent {
});
};
_onBugReport = () => {
const BugReportDialog = sdk.getComponent("dialogs.BugReportDialog");
if (!BugReportDialog) {
return;
}
private onBugReport = (): void => {
Modal.createTrackedDialog('Bug Report Dialog', '', BugReportDialog, {
label: 'react-soft-crash',
});
@ -74,7 +76,6 @@ export default class ErrorBoundary extends React.PureComponent {
render() {
if (this.state.error) {
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
const newIssueUrl = "https://github.com/vector-im/element-web/issues/new";
let bugReportSection;
@ -95,7 +96,7 @@ export default class ErrorBoundary extends React.PureComponent {
"the rooms or groups you have visited and the usernames of " +
"other users. They do not contain messages.",
)}</p>
<AccessibleButton onClick={this._onBugReport} kind='primary'>
<AccessibleButton onClick={this.onBugReport} kind='primary'>
{_t("Submit debug logs")}
</AccessibleButton>
</React.Fragment>;
@ -105,7 +106,7 @@ export default class ErrorBoundary extends React.PureComponent {
<div className="mx_ErrorBoundary_body">
<h1>{_t("Something went wrong!")}</h1>
{ bugReportSection }
<AccessibleButton onClick={this._onClearCacheAndReload} kind='danger'>
<AccessibleButton onClick={this.onClearCacheAndReload} kind='danger'>
{_t("Clear cache and reload")}
</AccessibleButton>
</div>

View file

@ -14,13 +14,13 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React, {ReactChildren, useEffect} from 'react';
import {MatrixEvent} from "matrix-js-sdk/src/models/event";
import {RoomMember} from "matrix-js-sdk/src/models/room-member";
import React, { ReactNode, useEffect } from 'react';
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
import { RoomMember } from "matrix-js-sdk/src/models/room-member";
import MemberAvatar from '../avatars/MemberAvatar';
import { _t } from '../../../languageHandler';
import {useStateToggle} from "../../../hooks/useStateToggle";
import { useStateToggle } from "../../../hooks/useStateToggle";
import AccessibleButton from "./AccessibleButton";
interface IProps {
@ -29,13 +29,13 @@ interface IProps {
// The minimum number of events needed to trigger summarisation
threshold?: number;
// Whether or not to begin with state.expanded=true
startExpanded?: boolean,
startExpanded?: boolean;
// The list of room members for which to show avatars next to the summary
summaryMembers?: RoomMember[],
summaryMembers?: RoomMember[];
// The text to show as the summary of this event list
summaryText?: string,
summaryText?: string;
// An array of EventTiles to render when expanded
children: ReactChildren,
children: ReactNode[];
// Called when the event list expansion is toggled
onToggle?(): void;
}

View file

@ -17,13 +17,14 @@ limitations under the License.
import React from 'react';
import classnames from 'classnames';
import { MatrixEvent } from 'matrix-js-sdk/src/models/event';
import { RoomMember } from 'matrix-js-sdk/src/models/room-member';
import * as Avatar from '../../../Avatar';
import EventTile from '../rooms/EventTile';
import SettingsStore from "../../../settings/SettingsStore";
import {Layout} from "../../../settings/Layout";
import {UIFeature} from "../../../settings/UIFeature";
import {replaceableComponent} from "../../../utils/replaceableComponent";
import { Layout } from "../../../settings/Layout";
import { UIFeature } from "../../../settings/UIFeature";
import { replaceableComponent } from "../../../utils/replaceableComponent";
interface IProps {
/**
@ -72,7 +73,7 @@ export default class EventTilePreview extends React.Component<IProps, IState> {
};
}
private fakeEvent({message}: IState) {
private fakeEvent({ message }: IState) {
// Fake it till we make it
/* eslint-disable quote-props */
const rawEvent = {
@ -101,7 +102,8 @@ export default class EventTilePreview extends React.Component<IProps, IState> {
// Fake it more
event.sender = {
name: this.props.displayName,
name: this.props.displayName || this.props.userId,
rawDisplayName: this.props.displayName,
userId: this.props.userId,
getAvatarUrl: (..._) => {
return Avatar.avatarUrlForUser(
@ -110,7 +112,7 @@ export default class EventTilePreview extends React.Component<IProps, IState> {
);
},
getMxcAvatarUrl: () => this.props.avatarUrl,
};
} as RoomMember;
return event;
}

View file

@ -14,11 +14,11 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React, {InputHTMLAttributes, SelectHTMLAttributes, TextareaHTMLAttributes} from 'react';
import React, { InputHTMLAttributes, SelectHTMLAttributes, TextareaHTMLAttributes } from 'react';
import classNames from 'classnames';
import * as sdk from '../../../index';
import {debounce} from "lodash";
import {IFieldState, IValidationResult} from "./Validation";
import { debounce } from "lodash";
import { IFieldState, IValidationResult } from "./Validation";
// Invoke validation from user input (when typing, etc.) at most once every N ms.
const VALIDATION_THROTTLE_MS = 200;
@ -29,6 +29,11 @@ function getId() {
return `${BASE_ID}_${count++}`;
}
export interface IValidateOpts {
focused?: boolean;
allowEmpty?: boolean;
}
interface IProps {
// The field's ID, which binds the input and label together. Immutable.
id?: string;
@ -180,7 +185,7 @@ export default class Field extends React.PureComponent<PropShapes, IState> {
}
};
public async validate({ focused, allowEmpty = true }: {focused?: boolean, allowEmpty?: boolean}) {
public async validate({ focused, allowEmpty = true }: IValidateOpts) {
if (!this.props.onValidate) {
return;
}
@ -217,7 +222,7 @@ export default class Field extends React.PureComponent<PropShapes, IState> {
/* eslint @typescript-eslint/no-unused-vars: ["error", { "ignoreRestSiblings": true }] */
const { element, prefixComponent, postfixComponent, className, onValidate, children,
tooltipContent, forceValidity, tooltipClassName, list, validateOnBlur, validateOnChange, validateOnFocus,
...inputProps} = this.props;
...inputProps } = this.props;
// Set some defaults for the <input> element
const ref = input => this.input = input;
@ -229,7 +234,7 @@ export default class Field extends React.PureComponent<PropShapes, IState> {
inputProps.onBlur = this.onBlur;
// Appease typescript's inference
const inputProps_ = {...inputProps, ref, list};
const inputProps_ = { ...inputProps, ref, list };
const fieldInput = React.createElement(this.props.element, inputProps_, children);

View file

@ -19,9 +19,8 @@ import PropTypes from 'prop-types';
import FlairStore from '../../../stores/FlairStore';
import dis from '../../../dispatcher/dispatcher';
import MatrixClientContext from "../../../contexts/MatrixClientContext";
import {replaceableComponent} from "../../../utils/replaceableComponent";
import {mediaFromMxc} from "../../../customisations/Media";
import { replaceableComponent } from "../../../utils/replaceableComponent";
import { mediaFromMxc } from "../../../customisations/Media";
class FlairAvatar extends React.Component {
constructor() {

View file

@ -1,28 +0,0 @@
/*
Copyright 2019 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import AccessibleButton from "./AccessibleButton";
export default function FormButton(props) {
const {className, label, kind, ...restProps} = props;
const newClassName = (className || "") + " mx_FormButton";
const allProps = Object.assign({}, restProps,
{className: newClassName, kind: kind || "primary", children: [label]});
return React.createElement(AccessibleButton, allProps);
}
FormButton.propTypes = AccessibleButton.propTypes;

View file

@ -16,9 +16,9 @@ limitations under the License.
import React from 'react';
import SettingsStore from "../../../settings/SettingsStore";
import Draggable, {ILocationState} from './Draggable';
import Draggable, { ILocationState } from './Draggable';
import { SettingLevel } from "../../../settings/SettingLevel";
import {replaceableComponent} from "../../../utils/replaceableComponent";
import { replaceableComponent } from "../../../utils/replaceableComponent";
interface IProps {
// Current room

View file

@ -30,7 +30,7 @@ import SettingsStore from "../../../settings/SettingsStore";
import { formatFullDate } from "../../../DateUtils";
import dis from '../../../dispatcher/dispatcher';
import { replaceableComponent } from "../../../utils/replaceableComponent";
import { RoomPermalinkCreator } from "../../../utils/permalinks/Permalinks"
import { RoomPermalinkCreator } from "../../../utils/permalinks/Permalinks";
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
import { normalizeWheelEvent } from "../../../utils/Mouse";
@ -44,31 +44,31 @@ const ZOOM_COEFFICIENT = 0.0025;
const ZOOM_DISTANCE = 10;
interface IProps {
src: string, // the source of the image being displayed
name?: string, // the main title ('name') for the image
link?: string, // the link (if any) applied to the name of the image
width?: number, // width of the image src in pixels
height?: number, // height of the image src in pixels
fileSize?: number, // size of the image src in bytes
onFinished(): void, // callback when the lightbox is dismissed
src: string; // the source of the image being displayed
name?: string; // the main title ('name') for the image
link?: string; // the link (if any) applied to the name of the image
width?: number; // width of the image src in pixels
height?: number; // height of the image src in pixels
fileSize?: number; // size of the image src in bytes
onFinished(): void; // callback when the lightbox is dismissed
// the event (if any) that the Image is displaying. Used for event-specific stuff like
// redactions, senders, timestamps etc. Other descriptors are taken from the explicit
// properties above, which let us use lightboxes to display images which aren't associated
// with events.
mxEvent: MatrixEvent,
permalinkCreator: RoomPermalinkCreator,
mxEvent: MatrixEvent;
permalinkCreator: RoomPermalinkCreator;
}
interface IState {
zoom: number,
minZoom: number,
maxZoom: number,
rotation: number,
translationX: number,
translationY: number,
moving: boolean,
contextMenuDisplayed: boolean,
zoom: number;
minZoom: number;
maxZoom: number;
rotation: number;
translationX: number;
translationY: number;
moving: boolean;
contextMenuDisplayed: boolean;
}
@replaceableComponent("views.elements.ImageView")
@ -116,7 +116,7 @@ export default class ImageView extends React.Component<IProps, IState> {
private recalculateZoom = () => {
this.setZoomAndRotation();
}
};
private setZoomAndRotation = (inputRotation?: number) => {
const image = this.image.current;
@ -158,7 +158,7 @@ export default class ImageView extends React.Component<IProps, IState> {
rotation: rotation,
zoom: zoom,
});
}
};
private zoom(delta: number) {
const newZoom = this.state.zoom + delta;

View file

@ -18,9 +18,9 @@ limitations under the License.
import React from 'react';
import classNames from 'classnames';
import Tooltip, {Alignment} from './Tooltip';
import {_t} from "../../../languageHandler";
import {replaceableComponent} from "../../../utils/replaceableComponent";
import Tooltip, { Alignment } from './Tooltip';
import { _t } from "../../../languageHandler";
import { replaceableComponent } from "../../../utils/replaceableComponent";
interface ITooltipProps {
tooltip?: React.ReactNode;
@ -53,7 +53,7 @@ export default class InfoTooltip extends React.PureComponent<ITooltipProps, ISta
};
render() {
const {tooltip, children, tooltipClassName} = this.props;
const { tooltip, children, tooltipClassName } = this.props;
const title = _t("Information");
// Tooltip are forced on the right for a more natural feel to them on info icons

View file

@ -15,8 +15,8 @@ limitations under the License.
*/
import React from "react";
import {_t} from "../../../languageHandler";
import {replaceableComponent} from "../../../utils/replaceableComponent";
import { _t } from "../../../languageHandler";
import { replaceableComponent } from "../../../utils/replaceableComponent";
interface IProps {
w?: number;
@ -29,14 +29,14 @@ export default class InlineSpinner extends React.PureComponent<IProps> {
static defaultProps = {
w: 16,
h: 16,
}
};
render() {
return (
<div className="mx_InlineSpinner">
<div
className="mx_InlineSpinner_icon mx_Spinner_icon"
style={{width: this.props.w, height: this.props.h}}
style={{ width: this.props.w, height: this.props.h }}
aria-label={_t("Loading...")}
>
{this.props.children}

View file

@ -42,7 +42,7 @@ export default class InviteReason extends React.PureComponent<IProps, IState> {
this.setState({
hidden: false,
});
}
};
render() {
const classes = classNames({

View file

@ -1,5 +1,5 @@
/*
Copyright 2019 New Vector Ltd
Copyright 2019 - 2021 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -14,38 +14,33 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import PropTypes from "prop-types";
import React from "react";
import ToggleSwitch from "./ToggleSwitch";
import {replaceableComponent} from "../../../utils/replaceableComponent";
import { replaceableComponent } from "../../../utils/replaceableComponent";
interface IProps {
// The value for the toggle switch
value: boolean;
// The translated label for the switch
label: string;
// Whether or not to disable the toggle switch
disabled?: boolean;
// True to put the toggle in front of the label
// Default false.
toggleInFront?: boolean;
// Additional class names to append to the switch. Optional.
className?: string;
// The function to call when the value changes
onChange(checked: boolean): void;
}
@replaceableComponent("views.elements.LabelledToggleSwitch")
export default class LabelledToggleSwitch extends React.Component {
static propTypes = {
// The value for the toggle switch
value: PropTypes.bool.isRequired,
// The function to call when the value changes
onChange: PropTypes.func.isRequired,
// The translated label for the switch
label: PropTypes.string.isRequired,
// Whether or not to disable the toggle switch
disabled: PropTypes.bool,
// True to put the toggle in front of the label
// Default false.
toggleInFront: PropTypes.bool,
// Additional class names to append to the switch. Optional.
className: PropTypes.string,
};
export default class LabelledToggleSwitch extends React.PureComponent<IProps> {
render() {
// This is a minimal version of a SettingsFlag
let firstPart = <span className="mx_SettingsFlag_label">{this.props.label}</span>;
let firstPart = <span className="mx_SettingsFlag_label">{ this.props.label }</span>;
let secondPart = <ToggleSwitch
checked={this.props.value}
disabled={this.props.disabled}

View file

@ -22,7 +22,7 @@ import * as sdk from '../../../index';
import * as languageHandler from '../../../languageHandler';
import SettingsStore from "../../../settings/SettingsStore";
import { _t } from "../../../languageHandler";
import {replaceableComponent} from "../../../utils/replaceableComponent";
import { replaceableComponent } from "../../../utils/replaceableComponent";
function languageMatchesSearchQuery(query, language) {
if (language.label.toUpperCase().includes(query.toUpperCase())) return true;
@ -49,9 +49,9 @@ export default class LanguageDropdown extends React.Component {
if (a.label > b.label) return 1;
return 0;
});
this.setState({langs});
this.setState({ langs });
}).catch(() => {
this.setState({langs: ['en']});
this.setState({ langs: ['en'] });
});
if (!this.props.value) {

View file

@ -16,7 +16,7 @@ limitations under the License.
import React from "react";
import PropTypes from 'prop-types';
import {replaceableComponent} from "../../../utils/replaceableComponent";
import { replaceableComponent } from "../../../utils/replaceableComponent";
class ItemRange {
constructor(topCount, renderCount, bottomCount) {
@ -72,13 +72,13 @@ export default class LazyRenderList extends React.Component {
// only update render Range if the list has shrunk/grown and we need to adjust padding OR
// if the new range + overflowMargin isn't contained by the old anymore
if (listHasChangedSize || !state.renderRange || !state.renderRange.contains(intersectRange)) {
return {renderRange};
return { renderRange };
}
return null;
}
static getVisibleRangeFromProps(props) {
const {items, itemHeight, scrollTop, height} = props;
const { items, itemHeight, scrollTop, height } = props;
const length = items ? items.length : 0;
const topCount = Math.min(Math.max(0, Math.floor(scrollTop / itemHeight)), length);
const itemsAfterTop = length - topCount;
@ -89,9 +89,9 @@ export default class LazyRenderList extends React.Component {
}
render() {
const {itemHeight, items, renderItem} = this.props;
const {renderRange} = this.state;
const {topCount, renderCount, bottomCount} = renderRange;
const { itemHeight, items, renderItem } = this.props;
const { renderRange } = this.state;
const { topCount, renderCount, bottomCount } = renderRange;
const paddingTop = topCount * itemHeight;
const paddingBottom = bottomCount * itemHeight;
@ -102,7 +102,7 @@ export default class LazyRenderList extends React.Component {
const element = this.props.element || "div";
const elementProps = {
"style": {paddingTop: `${paddingTop}px`, paddingBottom: `${paddingBottom}px`},
"style": { paddingTop: `${paddingTop}px`, paddingBottom: `${paddingBottom}px` },
"className": this.props.className,
};
return React.createElement(element, elementProps, renderedItems.map(renderItem));

View file

@ -16,7 +16,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React, { ReactChildren } from 'react';
import React, { ComponentProps } from 'react';
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
import { RoomMember } from "matrix-js-sdk/src/models/room-member";
@ -26,21 +26,11 @@ import { isValid3pidInvite } from "../../../RoomInvite";
import EventListSummary from "./EventListSummary";
import { replaceableComponent } from "../../../utils/replaceableComponent";
interface IProps {
// An array of member events to summarise
events: MatrixEvent[];
interface IProps extends Omit<ComponentProps<typeof EventListSummary>, "summaryText" | "summaryMembers"> {
// The maximum number of names to show in either each summary e.g. 2 would result "A, B and 234 others left"
summaryLength?: number;
// The maximum number of avatars to display in the summary
avatarsMaxLength?: number;
// The minimum number of events needed to trigger summarisation
threshold?: number,
// Whether or not to begin with state.expanded=true
startExpanded?: boolean,
// An array of EventTiles to render when expanded
children: ReactChildren;
// Called when the MELS expansion is toggled
onToggle?(): void,
}
interface IUserEvents {
@ -66,6 +56,7 @@ enum TransitionType {
ChangedName = "changed_name",
ChangedAvatar = "changed_avatar",
NoChange = "no_change",
ServerAcl = "server_acl",
}
const SEP = ",";
@ -298,6 +289,12 @@ export default class MemberEventListSummary extends React.Component<IProps> {
? _t("%(severalUsers)smade no changes %(count)s times", { severalUsers: "", count: repeats })
: _t("%(oneUser)smade no changes %(count)s times", { oneUser: "", count: repeats });
break;
case "server_acl":
res = (userCount > 1)
? _t("%(severalUsers)schanged the server ACLs %(count)s times",
{ severalUsers: "", count: repeats })
: _t("%(oneUser)schanged the server ACLs %(count)s times", { oneUser: "", count: repeats });
break;
}
return res;
@ -324,6 +321,10 @@ export default class MemberEventListSummary extends React.Component<IProps> {
return TransitionType.Invited;
}
if (e.mxEvent.getType() === 'm.room.server_acl') {
return TransitionType.ServerAcl;
}
switch (e.mxEvent.getContent().membership) {
case 'invite': return TransitionType.Invited;
case 'ban': return TransitionType.Banned;
@ -410,19 +411,23 @@ export default class MemberEventListSummary extends React.Component<IProps> {
// Object mapping user IDs to an array of IUserEvents
const userEvents: Record<string, IUserEvents[]> = {};
eventsToRender.forEach((e, index) => {
const userId = e.getStateKey();
const userId = e.getType() === 'm.room.server_acl' ? e.getSender() : e.getStateKey();
// Initialise a user's events
if (!userEvents[userId]) {
userEvents[userId] = [];
}
if (e.target) {
if (e.getType() === 'm.room.server_acl') {
latestUserAvatarMember.set(userId, e.sender);
} else if (e.target) {
latestUserAvatarMember.set(userId, e.target);
}
let displayName = userId;
if (e.getType() === 'm.room.third_party_invite') {
displayName = e.getContent().display_name;
} else if (e.getType() === 'm.room.server_acl') {
displayName = e.sender.name;
} else if (e.target) {
displayName = e.target.name;
}

View file

@ -14,14 +14,14 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React, {useContext, useRef, useState} from 'react';
import {EventType} from 'matrix-js-sdk/src/@types/event';
import React, { useContext, useRef, useState } from 'react';
import { EventType } from 'matrix-js-sdk/src/@types/event';
import classNames from 'classnames';
import AccessibleButton from "./AccessibleButton";
import Spinner from "./Spinner";
import MatrixClientContext from "../../../contexts/MatrixClientContext";
import {useTimeout} from "../../../hooks/useTimeout";
import { useTimeout } from "../../../hooks/useTimeout";
import Analytics from "../../../Analytics";
import CountlyAnalytics from '../../../CountlyAnalytics';
import RoomContext from "../../../contexts/RoomContext";
@ -52,7 +52,7 @@ const MiniAvatarUploader: React.FC<IProps> = ({ hasAvatar, hasAvatarLabel, noAva
const label = (hasAvatar || busy) ? hasAvatarLabel : noAvatarLabel;
const {room} = useContext(RoomContext);
const { room } = useContext(RoomContext);
const canSetAvatar = room?.currentState.maySendStateEvent(EventType.RoomAvatar, cli.getUserId());
if (!canSetAvatar) return <React.Fragment>{ children }</React.Fragment>;

View file

@ -17,14 +17,14 @@ limitations under the License.
import React from 'react';
import ReactDOM from 'react-dom';
import PropTypes from 'prop-types';
import {throttle} from "lodash";
import { throttle } from "lodash";
import ResizeObserver from 'resize-observer-polyfill';
import dis from '../../../dispatcher/dispatcher';
import MatrixClientContext from "../../../contexts/MatrixClientContext";
import {MatrixClientPeg} from "../../../MatrixClientPeg";
import {isNullOrUndefined} from "matrix-js-sdk/src/utils";
import {replaceableComponent} from "../../../utils/replaceableComponent";
import { MatrixClientPeg } from "../../../MatrixClientPeg";
import { isNullOrUndefined } from "matrix-js-sdk/src/utils";
import { replaceableComponent } from "../../../utils/replaceableComponent";
// Shamelessly ripped off Modal.js. There's probably a better way
// of doing reusable widgets like dialog boxes & menus where we go and
@ -180,11 +180,11 @@ export default class PersistedElement extends React.Component {
width: parentRect.width + 'px',
height: parentRect.height + 'px',
});
}, 100, {trailing: true, leading: true});
}, 100, { trailing: true, leading: true });
render() {
return <div ref={this.collectChildContainer} />;
}
}
export const getPersistKey = (appId: string) => 'widget_' + appId;
export const getPersistKey = (appId) => 'widget_' + appId;

View file

@ -20,8 +20,8 @@ import RoomViewStore from '../../../stores/RoomViewStore';
import ActiveWidgetStore from '../../../stores/ActiveWidgetStore';
import WidgetUtils from '../../../utils/WidgetUtils';
import * as sdk from '../../../index';
import {MatrixClientPeg} from '../../../MatrixClientPeg';
import {replaceableComponent} from "../../../utils/replaceableComponent";
import { MatrixClientPeg } from '../../../MatrixClientPeg';
import { replaceableComponent } from "../../../utils/replaceableComponent";
@replaceableComponent("views.elements.PersistentApp")
export default class PersistentApp extends React.Component {

View file

@ -20,14 +20,14 @@ import classNames from 'classnames';
import { Room } from 'matrix-js-sdk/src/models/room';
import { RoomMember } from 'matrix-js-sdk/src/models/room-member';
import PropTypes from 'prop-types';
import {MatrixClientPeg} from '../../../MatrixClientPeg';
import { MatrixClientPeg } from '../../../MatrixClientPeg';
import FlairStore from "../../../stores/FlairStore";
import {getPrimaryPermalinkEntity, parseAppLocalLink} from "../../../utils/permalinks/Permalinks";
import { getPrimaryPermalinkEntity, parseAppLocalLink } from "../../../utils/permalinks/Permalinks";
import MatrixClientContext from "../../../contexts/MatrixClientContext";
import {Action} from "../../../dispatcher/actions";
import {mediaFromMxc} from "../../../customisations/Media";
import { Action } from "../../../dispatcher/actions";
import { mediaFromMxc } from "../../../customisations/Media";
import Tooltip from './Tooltip';
import {replaceableComponent} from "../../../utils/replaceableComponent";
import { replaceableComponent } from "../../../utils/replaceableComponent";
@replaceableComponent("views.elements.Pill")
class Pill extends React.Component {
@ -144,7 +144,7 @@ class Pill extends React.Component {
}
}
}
this.setState({resourceId, pillType, member, group, room});
this.setState({ resourceId, pillType, member, group, room });
}
componentDidMount() {
@ -180,13 +180,13 @@ class Pill extends React.Component {
member.rawDisplayName = resp.displayname;
member.events.member = {
getContent: () => {
return {avatar_url: resp.avatar_url};
return { avatar_url: resp.avatar_url };
},
getDirectionalContent: function() {
return this.getContent();
},
};
this.setState({member});
this.setState({ member });
}).catch((err) => {
console.error('Could not retrieve profile data for ' + userId + ':', err);
});
@ -253,7 +253,7 @@ class Pill extends React.Component {
break;
case Pill.TYPE_GROUP_MENTION: {
if (this.state.group) {
const {avatarUrl, groupId, name} = this.state.group;
const { avatarUrl, groupId, name } = this.state.group;
linkText = groupId;
if (this.props.shouldShowPillAvatar) {
@ -273,7 +273,7 @@ class Pill extends React.Component {
});
if (this.state.pillType) {
const {yOffset} = this.props;
const { yOffset } = this.props;
let tip;
if (this.state.hover && resource) {

View file

@ -19,8 +19,8 @@ import PropTypes from 'prop-types';
import * as Roles from '../../../Roles';
import { _t } from '../../../languageHandler';
import Field from "./Field";
import {Key} from "../../../Keyboard";
import {replaceableComponent} from "../../../utils/replaceableComponent";
import { Key } from "../../../Keyboard";
import { replaceableComponent } from "../../../utils/replaceableComponent";
@replaceableComponent("views.elements.PowerSelector")
export default class PowerSelector extends React.Component {
@ -97,15 +97,15 @@ export default class PowerSelector extends React.Component {
onSelectChange = event => {
const isCustom = event.target.value === "SELECT_VALUE_CUSTOM";
if (isCustom) {
this.setState({custom: true});
this.setState({ custom: true });
} else {
this.props.onChange(event.target.value, this.props.powerLevelKey);
this.setState({selectValue: event.target.value});
this.setState({ selectValue: event.target.value });
}
};
onCustomChange = event => {
this.setState({customValue: event.target.value});
this.setState({ customValue: event.target.value });
};
onCustomBlur = event => {

View file

@ -21,7 +21,7 @@ interface IProps {
max: number;
}
const ProgressBar: React.FC<IProps> = ({value, max}) => {
const ProgressBar: React.FC<IProps> = ({ value, max }) => {
return <progress className="mx_ProgressBar" max={max} value={value} />;
};

View file

@ -15,10 +15,10 @@ limitations under the License.
*/
import * as React from "react";
import {toDataURL, QRCodeSegment, QRCodeToDataURLOptions} from "qrcode";
import { toDataURL, QRCodeSegment, QRCodeToDataURLOptions } from "qrcode";
import classNames from "classnames";
import {_t} from "../../../languageHandler";
import { _t } from "../../../languageHandler";
import Spinner from "./Spinner";
interface IProps extends QRCodeToDataURLOptions {
@ -30,11 +30,11 @@ const defaultOptions: QRCodeToDataURLOptions = {
errorCorrectionLevel: 'L', // we want it as trivial-looking as possible
};
const QRCode: React.FC<IProps> = ({data, className, ...options}) => {
const QRCode: React.FC<IProps> = ({ data, className, ...options }) => {
const [dataUri, setUri] = React.useState<string>(null);
React.useEffect(() => {
let cancelled = false;
toDataURL(data, {...defaultOptions, ...options}).then(uri => {
toDataURL(data, { ...defaultOptions, ...options }).then(uri => {
if (cancelled) return;
setUri(uri);
});

View file

@ -17,21 +17,21 @@ limitations under the License.
*/
import React from 'react';
import * as sdk from '../../../index';
import {_t} from '../../../languageHandler';
import { _t } from '../../../languageHandler';
import PropTypes from 'prop-types';
import dis from '../../../dispatcher/dispatcher';
import {MatrixEvent} from 'matrix-js-sdk/src/models/event';
import {makeUserPermalink, RoomPermalinkCreator} from "../../../utils/permalinks/Permalinks";
import { MatrixEvent } from 'matrix-js-sdk/src/models/event';
import { makeUserPermalink, RoomPermalinkCreator } from "../../../utils/permalinks/Permalinks";
import SettingsStore from "../../../settings/SettingsStore";
import {LayoutPropType} from "../../../settings/Layout";
import { LayoutPropType } from "../../../settings/Layout";
import escapeHtml from "escape-html";
import MatrixClientContext from "../../../contexts/MatrixClientContext";
import { getUserNameColorClass } from "../../../utils/FormattingUtils";
import {Action} from "../../../dispatcher/actions";
import { Action } from "../../../dispatcher/actions";
import sanitizeHtml from "sanitize-html";
import {UIFeature} from "../../../settings/UIFeature";
import {PERMITTED_URL_SCHEMES} from "../../../HtmlUtils";
import {replaceableComponent} from "../../../utils/replaceableComponent";
import { UIFeature } from "../../../settings/UIFeature";
import { PERMITTED_URL_SCHEMES } from "../../../HtmlUtils";
import { replaceableComponent } from "../../../utils/replaceableComponent";
// This component does no cycle detection, simply because the only way to make such a cycle would be to
// craft event_id's, using a homeserver that generates predictable event IDs; even then the impact would
@ -130,7 +130,7 @@ export default class ReplyThread extends React.Component {
static getNestedReplyText(ev, permalinkCreator) {
if (!ev) return null;
let {body, formatted_body: html} = ev.getContent();
let { body, formatted_body: html } = ev.getContent();
if (this.getParentEventId(ev)) {
if (body) body = this.stripPlainReply(body);
}
@ -200,7 +200,7 @@ export default class ReplyThread extends React.Component {
return null;
}
return {body, html};
return { body, html };
}
static makeReplyMixIn(ev) {
@ -269,7 +269,7 @@ export default class ReplyThread extends React.Component {
};
async initialize() {
const {parentEv} = this.props;
const { parentEv } = this.props;
// at time of making this component we checked that props.parentEv has a parentEventId
const ev = await this.getEvent(ReplyThread.getParentEventId(parentEv));
@ -283,7 +283,7 @@ export default class ReplyThread extends React.Component {
loading: false,
});
} else {
this.setState({err: true});
this.setState({ err: true });
}
}

View file

@ -1,5 +1,5 @@
/*
Copyright 2019 New Vector Ltd
Copyright 2019 - 2021 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -13,67 +13,78 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import React, { createRef } from "react";
import { _t } from '../../../languageHandler';
import React from 'react';
import PropTypes from 'prop-types';
import * as sdk from '../../../index';
import withValidation from './Validation';
import {MatrixClientPeg} from '../../../MatrixClientPeg';
import {replaceableComponent} from "../../../utils/replaceableComponent";
import { MatrixClientPeg } from '../../../MatrixClientPeg';
import { replaceableComponent } from "../../../utils/replaceableComponent";
import Field, { IValidateOpts } from "./Field";
interface IProps {
domain: string;
value: string;
label?: string;
placeholder?: string;
onChange?(value: string): void;
}
interface IState {
isValid: boolean;
}
// Controlled form component wrapping Field for inputting a room alias scoped to a given domain
@replaceableComponent("views.elements.RoomAliasField")
export default class RoomAliasField extends React.PureComponent {
static propTypes = {
domain: PropTypes.string.isRequired,
onChange: PropTypes.func,
value: PropTypes.string.isRequired,
};
export default class RoomAliasField extends React.PureComponent<IProps, IState> {
private fieldRef = createRef<Field>();
constructor(props) {
super(props);
this.state = {isValid: true};
constructor(props, context) {
super(props, context);
this.state = {
isValid: true,
};
}
_asFullAlias(localpart) {
private asFullAlias(localpart: string): string {
return `#${localpart}:${this.props.domain}`;
}
render() {
const Field = sdk.getComponent('views.elements.Field');
const poundSign = (<span>#</span>);
const aliasPostfix = ":" + this.props.domain;
const domain = (<span title={aliasPostfix}>{aliasPostfix}</span>);
const maxlength = 255 - this.props.domain.length - 2; // 2 for # and :
return (
<Field
label={_t("Room address")}
label={this.props.label || _t("Room address")}
className="mx_RoomAliasField"
prefixComponent={poundSign}
postfixComponent={domain}
ref={ref => this._fieldRef = ref}
onValidate={this._onValidate}
placeholder={_t("e.g. my-room")}
onChange={this._onChange}
ref={this.fieldRef}
onValidate={this.onValidate}
placeholder={this.props.placeholder || _t("e.g. my-room")}
onChange={this.onChange}
value={this.props.value.substring(1, this.props.value.length - this.props.domain.length - 1)}
maxLength={maxlength}
/>
);
}
_onChange = (ev) => {
private onChange = (ev) => {
if (this.props.onChange) {
this.props.onChange(this._asFullAlias(ev.target.value));
this.props.onChange(this.asFullAlias(ev.target.value));
}
};
_onValidate = async (fieldState) => {
const result = await this._validationRules(fieldState);
this.setState({isValid: result.valid});
private onValidate = async (fieldState) => {
const result = await this.validationRules(fieldState);
this.setState({ isValid: result.valid });
return result;
};
_validationRules = withValidation({
private validationRules = withValidation({
rules: [
{
key: "safeLocalpart",
@ -81,7 +92,7 @@ export default class RoomAliasField extends React.PureComponent {
if (!value) {
return true;
}
const fullAlias = this._asFullAlias(value);
const fullAlias = this.asFullAlias(value);
// XXX: FIXME https://github.com/matrix-org/matrix-doc/issues/668
return !value.includes("#") && !value.includes(":") && !value.includes(",") &&
encodeURI(fullAlias) === fullAlias;
@ -90,17 +101,17 @@ export default class RoomAliasField extends React.PureComponent {
}, {
key: "required",
test: async ({ value, allowEmpty }) => allowEmpty || !!value,
invalid: () => _t("Please provide a room address"),
invalid: () => _t("Please provide an address"),
}, {
key: "taken",
final: true,
test: async ({value}) => {
test: async ({ value }) => {
if (!value) {
return true;
}
const client = MatrixClientPeg.get();
try {
await client.getRoomIdForAlias(this._asFullAlias(value));
await client.getRoomIdForAlias(this.asFullAlias(value));
// we got a room id, so the alias is taken
return false;
} catch (err) {
@ -116,15 +127,15 @@ export default class RoomAliasField extends React.PureComponent {
],
});
get isValid() {
public get isValid() {
return this.state.isValid;
}
validate(options) {
return this._fieldRef.validate(options);
public validate(options: IValidateOpts) {
return this.fieldRef.current?.validate(options);
}
focus() {
this._fieldRef.focus();
public focus() {
this.fieldRef.current?.focus();
}
}

View file

@ -14,10 +14,10 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import {useEffect, useState} from "react";
import {Room} from "matrix-js-sdk/src/models/room";
import React, { useEffect, useState } from "react";
import { Room } from "matrix-js-sdk/src/models/room";
import {useEventEmitter} from "../../../hooks/useEventEmitter";
import { useEventEmitter } from "../../../hooks/useEventEmitter";
interface IProps {
room: Room;
@ -34,7 +34,7 @@ const RoomName = ({ room, children }: IProps): JSX.Element => {
}, [room]);
if (children) return children(name);
return name || "";
return <>{ name || "" }</>;
};
export default RoomName;

View file

@ -14,12 +14,12 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React, {useEffect, useState} from "react";
import {EventType} from "matrix-js-sdk/src/@types/event";
import {Room} from "matrix-js-sdk/src/models/room";
import React, { useEffect, useState } from "react";
import { EventType } from "matrix-js-sdk/src/@types/event";
import { Room } from "matrix-js-sdk/src/models/room";
import {useEventEmitter} from "../../../hooks/useEventEmitter";
import {linkifyElement} from "../../../HtmlUtils";
import { useEventEmitter } from "../../../hooks/useEventEmitter";
import { linkifyElement } from "../../../HtmlUtils";
interface IProps {
room?: Room;

View file

@ -17,14 +17,14 @@ limitations under the License.
import React from "react";
import { chunk } from "lodash";
import classNames from "classnames";
import {MatrixClient} from "matrix-js-sdk/src/client";
import { MatrixClient } from "matrix-js-sdk/src/client";
import PlatformPeg from "../../../PlatformPeg";
import AccessibleButton from "./AccessibleButton";
import {_t} from "../../../languageHandler";
import {IdentityProviderBrand, IIdentityProvider, ISSOFlow} from "../../../Login";
import { _t } from "../../../languageHandler";
import { IdentityProviderBrand, IIdentityProvider, ISSOFlow } from "../../../Login";
import AccessibleTooltipButton from "./AccessibleTooltipButton";
import {mediaFromMxc} from "../../../customisations/Media";
import { mediaFromMxc } from "../../../customisations/Media";
interface ISSOButtonProps extends Omit<IProps, "flow"> {
idp: IIdentityProvider;
@ -48,7 +48,7 @@ const getIcon = (brand: IdentityProviderBrand | string) => {
default:
return null;
}
}
};
const SSOButton: React.FC<ISSOButtonProps> = ({
matrixClient,
@ -111,7 +111,7 @@ interface IProps {
const MAX_PER_ROW = 6;
const SSOButtons: React.FC<IProps> = ({matrixClient, flow, loginType, fragmentAfterLogin, primary}) => {
const SSOButtons: React.FC<IProps> = ({ matrixClient, flow, loginType, fragmentAfterLogin, primary }) => {
const providers = flow.identity_providers || [];
if (providers.length < 2) {
return <div className="mx_SSOButtons">

View file

@ -17,8 +17,8 @@ limitations under the License.
import React from 'react';
import AccessibleButton from "./AccessibleButton";
import {ValidatedServerConfig} from "../../../utils/AutoDiscoveryUtils";
import {_t} from "../../../languageHandler";
import { ValidatedServerConfig } from "../../../utils/AutoDiscoveryUtils";
import { _t } from "../../../languageHandler";
import TextWithTooltip from "./TextWithTooltip";
import SdkConfig from "../../../SdkConfig";
import Modal from "../../../Modal";
@ -87,7 +87,7 @@ const ServerPicker = ({ title, dialogTitle, serverConfig, onServerConfigChange }
<span className="mx_ServerPicker_server">{serverName}</span>
{ editBtn }
{ desc }
</div>
}
</div>;
};
export default ServerPicker;

View file

@ -21,7 +21,7 @@ import { _t } from '../../../languageHandler';
import ToggleSwitch from "./ToggleSwitch";
import StyledCheckbox from "./StyledCheckbox";
import { SettingLevel } from "../../../settings/SettingLevel";
import {replaceableComponent} from "../../../utils/replaceableComponent";
import { replaceableComponent } from "../../../utils/replaceableComponent";
interface IProps {
// The setting must be a boolean
@ -77,9 +77,10 @@ export default class SettingsFlag extends React.Component<IProps, IState> {
public render() {
const canChange = SettingsStore.canSetValue(this.props.name, this.props.roomId, this.props.level);
let label = this.props.label;
if (!label) label = SettingsStore.getDisplayName(this.props.name, this.props.level);
else label = _t(label);
const label = this.props.label
? _t(this.props.label)
: SettingsStore.getDisplayName(this.props.name, this.props.level);
const description = SettingsStore.getDescription(this.props.name);
if (this.props.useCheckbox) {
return <StyledCheckbox
@ -99,6 +100,9 @@ export default class SettingsFlag extends React.Component<IProps, IState> {
disabled={this.props.disabled || !canChange}
aria-label={label}
/>
{ description && <div className="mx_SettingsFlag_microcopy">
{ description }
</div> }
</div>
);
}

View file

@ -15,7 +15,7 @@ limitations under the License.
*/
import * as React from 'react';
import {replaceableComponent} from "../../../utils/replaceableComponent";
import { replaceableComponent } from "../../../utils/replaceableComponent";
interface IProps {
// A callback for the selected value
@ -86,8 +86,8 @@ export default class Slider extends React.Component<IProps> {
if (!this.props.disabled) {
const offset = this.offset(this.props.values, this.props.value);
selection = <div className="mx_Slider_selection">
<div className="mx_Slider_selectionDot" style={{left: "calc(-0.55em + " + offset + "%)"}} />
<hr style={{width: offset + "%"}} />
<div className="mx_Slider_selectionDot" style={{ left: "calc(-0.55em + " + offset + "%)" }} />
<hr style={{ width: offset + "%" }} />
</div>;
}

View file

@ -16,12 +16,12 @@ limitations under the License.
import React from 'react';
import Dropdown from "../../views/elements/Dropdown"
import Dropdown from "../../views/elements/Dropdown";
import * as sdk from '../../../index';
import PlatformPeg from "../../../PlatformPeg";
import SettingsStore from "../../../settings/SettingsStore";
import { _t } from "../../../languageHandler";
import {replaceableComponent} from "../../../utils/replaceableComponent";
import { replaceableComponent } from "../../../utils/replaceableComponent";
function languageMatchesSearchQuery(query, language) {
if (language.label.toUpperCase().includes(query.toUpperCase())) return true;
@ -30,14 +30,14 @@ function languageMatchesSearchQuery(query, language) {
}
interface SpellCheckLanguagesDropdownIProps {
className: string,
value: string,
onOptionChange(language: string),
className: string;
value: string;
onOptionChange(language: string);
}
interface SpellCheckLanguagesDropdownIState {
searchQuery: string,
languages: any,
searchQuery: string;
languages: any;
}
@replaceableComponent("views.elements.SpellCheckLanguagesDropdown")
@ -67,11 +67,11 @@ export default class SpellCheckLanguagesDropdown extends React.Component<SpellCh
langs.push({
label: language,
value: language,
})
})
this.setState({languages: langs});
});
});
this.setState({ languages: langs });
}).catch((e) => {
this.setState({languages: ['en']});
this.setState({ languages: ['en'] });
});
}
}

View file

@ -17,14 +17,14 @@ limitations under the License.
import React from "react";
import PropTypes from "prop-types";
import {_t} from "../../../languageHandler";
import { _t } from "../../../languageHandler";
const Spinner = ({w = 32, h = 32, message}) => (
const Spinner = ({ w = 32, h = 32, message }) => (
<div className="mx_Spinner">
{ message && <React.Fragment><div className="mx_Spinner_Msg">{ message }</div>&nbsp;</React.Fragment> }
<div
className="mx_Spinner_icon"
style={{width: w, height: h}}
style={{ width: w, height: h }}
aria-label={_t("Loading...")}
></div>
</div>

View file

@ -15,7 +15,7 @@
*/
import React from 'react';
import {replaceableComponent} from "../../../utils/replaceableComponent";
import { replaceableComponent } from "../../../utils/replaceableComponent";
@replaceableComponent("views.elements.Spoiler")
export default class Spoiler extends React.Component {

View file

@ -16,7 +16,7 @@ limitations under the License.
import React from "react";
import { randomString } from "matrix-js-sdk/src/randomstring";
import {replaceableComponent} from "../../../utils/replaceableComponent";
import { replaceableComponent } from "../../../utils/replaceableComponent";
interface IProps extends React.InputHTMLAttributes<HTMLInputElement> {
}

View file

@ -16,7 +16,7 @@ limitations under the License.
import React from 'react';
import classnames from 'classnames';
import {replaceableComponent} from "../../../utils/replaceableComponent";
import { replaceableComponent } from "../../../utils/replaceableComponent";
interface IProps extends React.InputHTMLAttributes<HTMLInputElement> {
outlined?: boolean;

View file

@ -34,10 +34,19 @@ interface IProps<T extends string> {
definitions: IDefinition<T>[];
value?: T; // if not provided no options will be selected
outlined?: boolean;
disabled?: boolean;
onChange(newValue: T): void;
}
function StyledRadioGroup<T extends string>({name, definitions, value, className, outlined, onChange}: IProps<T>) {
function StyledRadioGroup<T extends string>({
name,
definitions,
value,
className,
outlined,
disabled,
onChange,
}: IProps<T>) {
const _onChange = e => {
onChange(e.target.value);
};
@ -50,12 +59,12 @@ function StyledRadioGroup<T extends string>({name, definitions, value, className
checked={d.checked !== undefined ? d.checked : d.value === value}
name={name}
value={d.value}
disabled={d.disabled}
disabled={disabled || d.disabled}
outlined={outlined}
>
{d.label}
{ d.label }
</StyledRadioButton>
{d.description}
{ d.description ? <span>{ d.description }</span> : null }
</React.Fragment>)}
</React.Fragment>;
}

View file

@ -16,8 +16,8 @@ limitations under the License.
import React from 'react';
import PropTypes from 'prop-types';
import {highlightBlock} from 'highlight.js';
import {replaceableComponent} from "../../../utils/replaceableComponent";
import { highlightBlock } from 'highlight.js';
import { replaceableComponent } from "../../../utils/replaceableComponent";
@replaceableComponent("views.elements.SyntaxHighlight")
export default class SyntaxHighlight extends React.Component {

View file

@ -30,8 +30,8 @@ import GroupFilterOrderStore from '../../../stores/GroupFilterOrderStore';
import MatrixClientContext from "../../../contexts/MatrixClientContext";
import AccessibleButton from "./AccessibleButton";
import SettingsStore from "../../../settings/SettingsStore";
import {mediaFromMxc} from "../../../customisations/Media";
import {replaceableComponent} from "../../../utils/replaceableComponent";
import { mediaFromMxc } from "../../../customisations/Media";
import { replaceableComponent } from "../../../utils/replaceableComponent";
// A class for a child of GroupFilterPanel (possibly wrapped in a DNDTagTile) that represents
// a thing to click on for the user to filter the visible rooms in the RoomList to:

View file

@ -17,7 +17,7 @@
import React from 'react';
import PropTypes from 'prop-types';
import * as sdk from '../../../index';
import {replaceableComponent} from "../../../utils/replaceableComponent";
import { replaceableComponent } from "../../../utils/replaceableComponent";
@replaceableComponent("views.elements.TextWithTooltip")
export default class TextWithTooltip extends React.Component {
@ -37,17 +37,17 @@ export default class TextWithTooltip extends React.Component {
}
onMouseOver = () => {
this.setState({hover: true});
this.setState({ hover: true });
};
onMouseLeave = () => {
this.setState({hover: false});
this.setState({ hover: false });
};
render() {
const Tooltip = sdk.getComponent("elements.Tooltip");
const {class: className, children, tooltip, tooltipClass, tooltipProps, ...props} = this.props;
const { class: className, children, tooltip, tooltipClass, tooltipProps, ...props } = this.props;
return (
<span {...props} onMouseOver={this.onMouseOver} onMouseLeave={this.onMouseLeave} className={className}>

View file

@ -1,82 +0,0 @@
/*
Copyright 2015 OpenMarket Ltd
Copyright 2019 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import PropTypes from 'prop-types';
import Tinter from "../../../Tinter";
import {replaceableComponent} from "../../../utils/replaceableComponent";
@replaceableComponent("views.elements.TintableSvg")
class TintableSvg extends React.Component {
static propTypes = {
src: PropTypes.string.isRequired,
width: PropTypes.string.isRequired,
height: PropTypes.string.isRequired,
className: PropTypes.string,
};
// list of currently mounted TintableSvgs
static mounts = {};
static idSequence = 0;
componentDidMount() {
this.fixups = [];
this.id = TintableSvg.idSequence++;
TintableSvg.mounts[this.id] = this;
}
componentWillUnmount() {
delete TintableSvg.mounts[this.id];
}
tint = () => {
// TODO: only bother running this if the global tint settings have changed
// since we loaded!
Tinter.applySvgFixups(this.fixups);
};
onLoad = event => {
// console.log("TintableSvg.onLoad for " + this.props.src);
this.fixups = Tinter.calcSvgFixups([event.target]);
Tinter.applySvgFixups(this.fixups);
};
render() {
return (
<object className={"mx_TintableSvg " + (this.props.className ? this.props.className : "")}
type="image/svg+xml"
data={this.props.src}
width={this.props.width}
height={this.props.height}
onLoad={this.onLoad}
tabIndex="-1"
/>
);
}
}
// Register with the Tinter so that we will be told if the tint changes
Tinter.registerTintable(function() {
if (TintableSvg.mounts) {
Object.keys(TintableSvg.mounts).forEach((id) => {
TintableSvg.mounts[id].tint();
});
}
});
export default TintableSvg;

View file

@ -31,7 +31,7 @@ interface IProps {
}
// Controlled Toggle Switch element, written with Accessibility in mind
export default ({checked, disabled = false, onChange, ...props}: IProps) => {
export default ({ checked, disabled = false, onChange, ...props }: IProps) => {
const _onClick = () => {
if (disabled) return;
onChange(!checked);

View file

@ -17,11 +17,10 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React, {Component, CSSProperties} from 'react';
import React, { Component, CSSProperties } from 'react';
import ReactDOM from 'react-dom';
import classNames from 'classnames';
import {replaceableComponent} from "../../../utils/replaceableComponent";
import { replaceableComponent } from "../../../utils/replaceableComponent";
import UIStore from "../../../stores/UIStore";
const MIN_TOOLTIP_HEIGHT = 25;

View file

@ -17,7 +17,7 @@ limitations under the License.
import React from 'react';
import * as sdk from '../../../index';
import {replaceableComponent} from "../../../utils/replaceableComponent";
import { replaceableComponent } from "../../../utils/replaceableComponent";
interface IProps {
helpText: string;

View file

@ -16,41 +16,39 @@ limitations under the License.
*/
import React from 'react';
import PropTypes from 'prop-types';
import { _t } from '../../../languageHandler';
import {replaceableComponent} from "../../../utils/replaceableComponent";
import { replaceableComponent } from "../../../utils/replaceableComponent";
interface IProps {
// The number of elements to show before truncating. If negative, no truncation is done.
truncateAt?: number;
// The className to apply to the wrapping div
className?: string;
// A function that returns the children to be rendered into the element.
// The start element is included, the end is not (as in `slice`).
// If omitted, the React child elements will be used. This parameter can be used
// to avoid creating unnecessary React elements.
getChildren?: (start: number, end: number) => Array<React.ReactNode>;
// A function that should return the total number of child element available.
// Required if getChildren is supplied.
getChildCount?: () => number;
// A function which will be invoked when an overflow element is required.
// This will be inserted after the children.
createOverflowElement?: (overflowCount: number, totalCount: number) => React.ReactNode;
}
@replaceableComponent("views.elements.TruncatedList")
export default class TruncatedList extends React.Component {
static propTypes = {
// The number of elements to show before truncating. If negative, no truncation is done.
truncateAt: PropTypes.number,
// The className to apply to the wrapping div
className: PropTypes.string,
// A function that returns the children to be rendered into the element.
// function getChildren(start: number, end: number): Array<React.Node>
// The start element is included, the end is not (as in `slice`).
// If omitted, the React child elements will be used. This parameter can be used
// to avoid creating unnecessary React elements.
getChildren: PropTypes.func,
// A function that should return the total number of child element available.
// Required if getChildren is supplied.
getChildCount: PropTypes.func,
// A function which will be invoked when an overflow element is required.
// This will be inserted after the children.
createOverflowElement: PropTypes.func,
};
export default class TruncatedList extends React.Component<IProps> {
static defaultProps ={
truncateAt: 2,
createOverflowElement(overflowCount, totalCount) {
return (
<div>{ _t("And %(count)s more...", {count: overflowCount}) }</div>
<div>{ _t("And %(count)s more...", { count: overflowCount }) }</div>
);
},
};
_getChildren(start, end) {
private getChildren(start: number, end: number): Array<React.ReactNode> {
if (this.props.getChildren && this.props.getChildCount) {
return this.props.getChildren(start, end);
} else {
@ -63,7 +61,7 @@ export default class TruncatedList extends React.Component {
}
}
_getChildCount() {
private getChildCount(): number {
if (this.props.getChildren && this.props.getChildCount) {
return this.props.getChildCount();
} else {
@ -73,10 +71,10 @@ export default class TruncatedList extends React.Component {
}
}
render() {
public render() {
let overflowNode = null;
const totalChildren = this._getChildCount();
const totalChildren = this.getChildCount();
let upperBound = totalChildren;
if (this.props.truncateAt >= 0) {
const overflowCount = totalChildren - this.props.truncateAt;
@ -87,7 +85,7 @@ export default class TruncatedList extends React.Component {
upperBound = this.props.truncateAt;
}
}
const childNodes = this._getChildren(0, upperBound);
const childNodes = this.getChildren(0, upperBound);
return (
<div className={this.props.className}>

View file

@ -21,7 +21,7 @@ import GroupFilterOrderStore from "../../../stores/GroupFilterOrderStore";
import AccessibleTooltipButton from "./AccessibleTooltipButton";
import classNames from "classnames";
import { _t } from "../../../languageHandler";
import {replaceableComponent} from "../../../utils/replaceableComponent";
import { replaceableComponent } from "../../../utils/replaceableComponent";
interface IProps {
}
@ -52,7 +52,7 @@ export default class UserTagTile extends React.PureComponent<IProps, IState> {
private onTagStoreUpdate = () => {
const selected = GroupFilterOrderStore.getSelectedTags().length === 0;
this.setState({selected});
this.setState({ selected });
};
private onTileClick = (ev) => {
@ -60,7 +60,7 @@ export default class UserTagTile extends React.PureComponent<IProps, IState> {
ev.stopPropagation();
// Deselect all tags
defaultDispatcher.dispatch({action: "deselect_tags"});
defaultDispatcher.dispatch({ action: "deselect_tags" });
};
public render() {

View file

@ -15,7 +15,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
/* eslint-disable babel/no-invalid-this */
/* eslint-disable @typescript-eslint/no-invalid-this */
import React from "react";
import classNames from "classnames";

View file

@ -16,7 +16,7 @@ limitations under the License.
import React from "react";
import PropTypes from "prop-types";
import {replaceableComponent} from "../../../../utils/replaceableComponent";
import { replaceableComponent } from "../../../../utils/replaceableComponent";
import QRCode from "../QRCode";
@replaceableComponent("views.elements.crypto.VerificationQRCode")
@ -28,7 +28,7 @@ export default class VerificationQRCode extends React.PureComponent {
render() {
return (
<QRCode
data={[{data: this.props.qrCodeData.buffer, mode: 'byte'}]}
data={[{ data: this.props.qrCodeData.buffer, mode: 'byte' }]}
className="mx_VerificationQRCode"
width={196} />
);