Merge pull request #6787 from SimonBrandner/task/structures-ts

This commit is contained in:
Michael Telatynski 2021-09-16 10:45:12 +01:00 committed by GitHub
commit 3b39ba6590
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 316 additions and 284 deletions

View file

@ -17,7 +17,6 @@ limitations under the License.
*/ */
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types';
import request from 'browser-request'; import request from 'browser-request';
import { _t } from '../../languageHandler'; import { _t } from '../../languageHandler';
import sanitizeHtml from 'sanitize-html'; import sanitizeHtml from 'sanitize-html';
@ -26,38 +25,43 @@ import { MatrixClientPeg } from '../../MatrixClientPeg';
import classnames from 'classnames'; import classnames from 'classnames';
import MatrixClientContext from "../../contexts/MatrixClientContext"; import MatrixClientContext from "../../contexts/MatrixClientContext";
import AutoHideScrollbar from "./AutoHideScrollbar"; import AutoHideScrollbar from "./AutoHideScrollbar";
import { ActionPayload } from "../../dispatcher/payloads";
export default class EmbeddedPage extends React.PureComponent { interface IProps {
static propTypes = { // URL to request embedded page content from
// URL to request embedded page content from url?: string;
url: PropTypes.string, // Class name prefix to apply for a given instance
// Class name prefix to apply for a given instance className?: string;
className: PropTypes.string, // Whether to wrap the page in a scrollbar
// Whether to wrap the page in a scrollbar scrollbar?: boolean;
scrollbar: PropTypes.bool, // Map of keys to replace with values, e.g {$placeholder: "value"}
// Map of keys to replace with values, e.g {$placeholder: "value"} replaceMap?: Map<string, string>;
replaceMap: PropTypes.object, }
};
static contextType = MatrixClientContext; interface IState {
page: string;
}
constructor(props, context) { export default class EmbeddedPage extends React.PureComponent<IProps, IState> {
public static contextType = MatrixClientContext;
private unmounted = false;
private dispatcherRef: string = null;
constructor(props: IProps, context: typeof MatrixClientContext) {
super(props, context); super(props, context);
this._dispatcherRef = null;
this.state = { this.state = {
page: '', page: '',
}; };
} }
translate(s) { private translate(s: string): string {
// default implementation - skins may wish to extend this // default implementation - skins may wish to extend this
return sanitizeHtml(_t(s)); return sanitizeHtml(_t(s));
} }
componentDidMount() { public componentDidMount(): void {
this._unmounted = false; this.unmounted = false;
if (!this.props.url) { if (!this.props.url) {
return; return;
@ -70,7 +74,7 @@ export default class EmbeddedPage extends React.PureComponent {
request( request(
{ method: "GET", url: this.props.url }, { method: "GET", url: this.props.url },
(err, response, body) => { (err, response, body) => {
if (this._unmounted) { if (this.unmounted) {
return; return;
} }
@ -92,22 +96,22 @@ export default class EmbeddedPage extends React.PureComponent {
}, },
); );
this._dispatcherRef = dis.register(this.onAction); this.dispatcherRef = dis.register(this.onAction);
} }
componentWillUnmount() { public componentWillUnmount(): void {
this._unmounted = true; this.unmounted = true;
if (this._dispatcherRef !== null) dis.unregister(this._dispatcherRef); if (this.dispatcherRef !== null) dis.unregister(this.dispatcherRef);
} }
onAction = (payload) => { private onAction = (payload: ActionPayload): void => {
// HACK: Workaround for the context's MatrixClient not being set up at render time. // HACK: Workaround for the context's MatrixClient not being set up at render time.
if (payload.action === 'client_started') { if (payload.action === 'client_started') {
this.forceUpdate(); this.forceUpdate();
} }
}; };
render() { public render(): JSX.Element {
// HACK: Workaround for the context's MatrixClient not updating. // HACK: Workaround for the context's MatrixClient not updating.
const client = this.context || MatrixClientPeg.get(); const client = this.context || MatrixClientPeg.get();
const isGuest = client ? client.isGuest() : true; const isGuest = client ? client.isGuest() : true;

View file

@ -15,16 +15,15 @@ limitations under the License.
*/ */
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types';
import { replaceableComponent } from "../../utils/replaceableComponent"; import { replaceableComponent } from "../../utils/replaceableComponent";
@replaceableComponent("structures.GenericErrorPage") interface IProps {
export default class GenericErrorPage extends React.PureComponent { title: React.ReactNode;
static propTypes = { message: React.ReactNode;
title: PropTypes.object.isRequired, // jsx for title }
message: PropTypes.object.isRequired, // jsx to display
};
@replaceableComponent("structures.GenericErrorPage")
export default class GenericErrorPage extends React.PureComponent<IProps> {
render() { render() {
return <div className='mx_GenericErrorPage'> return <div className='mx_GenericErrorPage'>
<div className='mx_GenericErrorPage_box'> <div className='mx_GenericErrorPage_box'>

View file

@ -14,34 +14,39 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import React from "react"; import React, { createRef } from "react";
import PropTypes from "prop-types";
import AutoHideScrollbar from "./AutoHideScrollbar"; import AutoHideScrollbar from "./AutoHideScrollbar";
import { replaceableComponent } from "../../utils/replaceableComponent"; import { replaceableComponent } from "../../utils/replaceableComponent";
interface IProps {
// If true, the scrollbar will append mx_IndicatorScrollbar_leftOverflowIndicator
// and mx_IndicatorScrollbar_rightOverflowIndicator elements to the list for positioning
// by the parent element.
trackHorizontalOverflow?: boolean;
// If true, when the user tries to use their mouse wheel in the component it will
// scroll horizontally rather than vertically. This should only be used on components
// with no vertical scroll opportunity.
verticalScrollsHorizontally?: boolean;
children: React.ReactNode;
className: string;
}
interface IState {
leftIndicatorOffset: number | string;
rightIndicatorOffset: number | string;
}
@replaceableComponent("structures.IndicatorScrollbar") @replaceableComponent("structures.IndicatorScrollbar")
export default class IndicatorScrollbar extends React.Component { export default class IndicatorScrollbar extends React.Component<IProps, IState> {
static propTypes = { private autoHideScrollbar = createRef<AutoHideScrollbar>();
// If true, the scrollbar will append mx_IndicatorScrollbar_leftOverflowIndicator private scrollElement: HTMLDivElement;
// and mx_IndicatorScrollbar_rightOverflowIndicator elements to the list for positioning private likelyTrackpadUser: boolean = null;
// by the parent element. private checkAgainForTrackpad = 0; // ts in milliseconds to recheck this._likelyTrackpadUser
trackHorizontalOverflow: PropTypes.bool,
// If true, when the user tries to use their mouse wheel in the component it will constructor(props: IProps) {
// scroll horizontally rather than vertically. This should only be used on components
// with no vertical scroll opportunity.
verticalScrollsHorizontally: PropTypes.bool,
};
constructor(props) {
super(props); super(props);
this._collectScroller = this._collectScroller.bind(this);
this._collectScrollerComponent = this._collectScrollerComponent.bind(this);
this.checkOverflow = this.checkOverflow.bind(this);
this._scrollElement = null;
this._autoHideScrollbar = null;
this._likelyTrackpadUser = null;
this._checkAgainForTrackpad = 0; // ts in milliseconds to recheck this._likelyTrackpadUser
this.state = { this.state = {
leftIndicatorOffset: 0, leftIndicatorOffset: 0,
@ -49,30 +54,19 @@ export default class IndicatorScrollbar extends React.Component {
}; };
} }
moveToOrigin() { private collectScroller = (scroller: HTMLDivElement): void => {
if (!this._scrollElement) return; if (scroller && !this.scrollElement) {
this.scrollElement = scroller;
this._scrollElement.scrollLeft = 0;
this._scrollElement.scrollTop = 0;
}
_collectScroller(scroller) {
if (scroller && !this._scrollElement) {
this._scrollElement = scroller;
// Using the passive option to not block the main thread // Using the passive option to not block the main thread
// https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#improving_scrolling_performance_with_passive_listeners // https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#improving_scrolling_performance_with_passive_listeners
this._scrollElement.addEventListener("scroll", this.checkOverflow, { passive: true }); this.scrollElement.addEventListener("scroll", this.checkOverflow, { passive: true });
this.checkOverflow(); this.checkOverflow();
} }
} };
_collectScrollerComponent(autoHideScrollbar) { public componentDidUpdate(prevProps: IProps): void {
this._autoHideScrollbar = autoHideScrollbar; const prevLen = React.Children.count(prevProps.children);
} const curLen = React.Children.count(this.props.children);
componentDidUpdate(prevProps) {
const prevLen = prevProps && prevProps.children && prevProps.children.length || 0;
const curLen = this.props.children && this.props.children.length || 0;
// check overflow only if amount of children changes. // check overflow only if amount of children changes.
// if we don't guard here, we end up with an infinite // if we don't guard here, we end up with an infinite
// render > componentDidUpdate > checkOverflow > setState > render loop // render > componentDidUpdate > checkOverflow > setState > render loop
@ -81,62 +75,58 @@ export default class IndicatorScrollbar extends React.Component {
} }
} }
componentDidMount() { public componentDidMount(): void {
this.checkOverflow(); this.checkOverflow();
} }
checkOverflow() { private checkOverflow = (): void => {
const hasTopOverflow = this._scrollElement.scrollTop > 0; const hasTopOverflow = this.scrollElement.scrollTop > 0;
const hasBottomOverflow = this._scrollElement.scrollHeight > const hasBottomOverflow = this.scrollElement.scrollHeight >
(this._scrollElement.scrollTop + this._scrollElement.clientHeight); (this.scrollElement.scrollTop + this.scrollElement.clientHeight);
const hasLeftOverflow = this._scrollElement.scrollLeft > 0; const hasLeftOverflow = this.scrollElement.scrollLeft > 0;
const hasRightOverflow = this._scrollElement.scrollWidth > const hasRightOverflow = this.scrollElement.scrollWidth >
(this._scrollElement.scrollLeft + this._scrollElement.clientWidth); (this.scrollElement.scrollLeft + this.scrollElement.clientWidth);
if (hasTopOverflow) { if (hasTopOverflow) {
this._scrollElement.classList.add("mx_IndicatorScrollbar_topOverflow"); this.scrollElement.classList.add("mx_IndicatorScrollbar_topOverflow");
} else { } else {
this._scrollElement.classList.remove("mx_IndicatorScrollbar_topOverflow"); this.scrollElement.classList.remove("mx_IndicatorScrollbar_topOverflow");
} }
if (hasBottomOverflow) { if (hasBottomOverflow) {
this._scrollElement.classList.add("mx_IndicatorScrollbar_bottomOverflow"); this.scrollElement.classList.add("mx_IndicatorScrollbar_bottomOverflow");
} else { } else {
this._scrollElement.classList.remove("mx_IndicatorScrollbar_bottomOverflow"); this.scrollElement.classList.remove("mx_IndicatorScrollbar_bottomOverflow");
} }
if (hasLeftOverflow) { if (hasLeftOverflow) {
this._scrollElement.classList.add("mx_IndicatorScrollbar_leftOverflow"); this.scrollElement.classList.add("mx_IndicatorScrollbar_leftOverflow");
} else { } else {
this._scrollElement.classList.remove("mx_IndicatorScrollbar_leftOverflow"); this.scrollElement.classList.remove("mx_IndicatorScrollbar_leftOverflow");
} }
if (hasRightOverflow) { if (hasRightOverflow) {
this._scrollElement.classList.add("mx_IndicatorScrollbar_rightOverflow"); this.scrollElement.classList.add("mx_IndicatorScrollbar_rightOverflow");
} else { } else {
this._scrollElement.classList.remove("mx_IndicatorScrollbar_rightOverflow"); this.scrollElement.classList.remove("mx_IndicatorScrollbar_rightOverflow");
} }
if (this.props.trackHorizontalOverflow) { if (this.props.trackHorizontalOverflow) {
this.setState({ this.setState({
// Offset from absolute position of the container // Offset from absolute position of the container
leftIndicatorOffset: hasLeftOverflow ? `${this._scrollElement.scrollLeft}px` : '0', leftIndicatorOffset: hasLeftOverflow ? `${this.scrollElement.scrollLeft}px` : '0',
// Negative because we're coming from the right // Negative because we're coming from the right
rightIndicatorOffset: hasRightOverflow ? `-${this._scrollElement.scrollLeft}px` : '0', rightIndicatorOffset: hasRightOverflow ? `-${this.scrollElement.scrollLeft}px` : '0',
}); });
} }
} };
getScrollTop() { public componentWillUnmount(): void {
return this._autoHideScrollbar.getScrollTop(); if (this.scrollElement) {
} this.scrollElement.removeEventListener("scroll", this.checkOverflow);
componentWillUnmount() {
if (this._scrollElement) {
this._scrollElement.removeEventListener("scroll", this.checkOverflow);
} }
} }
onMouseWheel = (e) => { private onMouseWheel = (e: React.WheelEvent): void => {
if (this.props.verticalScrollsHorizontally && this._scrollElement) { if (this.props.verticalScrollsHorizontally && this.scrollElement) {
// xyThreshold is the amount of horizontal motion required for the component to // xyThreshold is the amount of horizontal motion required for the component to
// ignore the vertical delta in a scroll. Used to stop trackpads from acting in // ignore the vertical delta in a scroll. Used to stop trackpads from acting in
// strange ways. Should be positive. // strange ways. Should be positive.
@ -150,19 +140,19 @@ export default class IndicatorScrollbar extends React.Component {
// for at least the next 1 minute. // for at least the next 1 minute.
const now = new Date().getTime(); const now = new Date().getTime();
if (Math.abs(e.deltaX) > 0) { if (Math.abs(e.deltaX) > 0) {
this._likelyTrackpadUser = true; this.likelyTrackpadUser = true;
this._checkAgainForTrackpad = now + (1 * 60 * 1000); this.checkAgainForTrackpad = now + (1 * 60 * 1000);
} else { } else {
// if we haven't seen any horizontal scrolling for a while, assume // if we haven't seen any horizontal scrolling for a while, assume
// the user might have plugged in a mousewheel // the user might have plugged in a mousewheel
if (this._likelyTrackpadUser && now >= this._checkAgainForTrackpad) { if (this.likelyTrackpadUser && now >= this.checkAgainForTrackpad) {
this._likelyTrackpadUser = false; this.likelyTrackpadUser = false;
} }
} }
// don't mess with the horizontal scroll for trackpad users // don't mess with the horizontal scroll for trackpad users
// See https://github.com/vector-im/element-web/issues/10005 // See https://github.com/vector-im/element-web/issues/10005
if (this._likelyTrackpadUser) { if (this.likelyTrackpadUser) {
return; return;
} }
@ -178,13 +168,13 @@ export default class IndicatorScrollbar extends React.Component {
// noinspection JSSuspiciousNameCombination // noinspection JSSuspiciousNameCombination
const val = Math.abs(e.deltaY) < 25 ? (e.deltaY + additionalScroll) : e.deltaY; const val = Math.abs(e.deltaY) < 25 ? (e.deltaY + additionalScroll) : e.deltaY;
this._scrollElement.scrollLeft += val * yRetention; this.scrollElement.scrollLeft += val * yRetention;
} }
} }
}; };
render() { public render(): JSX.Element {
// eslint-disable-next-line no-unused-vars // eslint-disable-next-line @typescript-eslint/no-unused-vars
const { children, trackHorizontalOverflow, verticalScrollsHorizontally, ...otherProps } = this.props; const { children, trackHorizontalOverflow, verticalScrollsHorizontally, ...otherProps } = this.props;
const leftIndicatorStyle = { left: this.state.leftIndicatorOffset }; const leftIndicatorStyle = { left: this.state.leftIndicatorOffset };
@ -195,8 +185,8 @@ export default class IndicatorScrollbar extends React.Component {
? <div className="mx_IndicatorScrollbar_rightOverflowIndicator" style={rightIndicatorStyle} /> : null; ? <div className="mx_IndicatorScrollbar_rightOverflowIndicator" style={rightIndicatorStyle} /> : null;
return (<AutoHideScrollbar return (<AutoHideScrollbar
ref={this._collectScrollerComponent} ref={this.autoHideScrollbar}
wrappedRef={this._collectScroller} wrappedRef={this.collectScroller}
onWheel={this.onMouseWheel} onWheel={this.onMouseWheel}
{...otherProps} {...otherProps}
> >

View file

@ -16,25 +16,35 @@ limitations under the License.
*/ */
import React from 'react'; import React from 'react';
import { Resizable } from 're-resizable'; import { NumberSize, Resizable } from 're-resizable';
import { replaceableComponent } from "../../utils/replaceableComponent"; import { replaceableComponent } from "../../utils/replaceableComponent";
import ResizeNotifier from "../../utils/ResizeNotifier";
import { Direction } from "re-resizable/lib/resizer";
interface IProps {
resizeNotifier: ResizeNotifier;
collapsedRhs?: boolean;
panel: JSX.Element;
}
@replaceableComponent("structures.MainSplit") @replaceableComponent("structures.MainSplit")
export default class MainSplit extends React.Component { export default class MainSplit extends React.Component<IProps> {
_onResizeStart = () => { private onResizeStart = (): void => {
this.props.resizeNotifier.startResizing(); this.props.resizeNotifier.startResizing();
}; };
_onResize = () => { private onResize = (): void => {
this.props.resizeNotifier.notifyRightHandleResized(); this.props.resizeNotifier.notifyRightHandleResized();
}; };
_onResizeStop = (event, direction, refToElement, delta) => { private onResizeStop = (
event: MouseEvent | TouchEvent, direction: Direction, elementRef: HTMLElement, delta: NumberSize,
): void => {
this.props.resizeNotifier.stopResizing(); this.props.resizeNotifier.stopResizing();
window.localStorage.setItem("mx_rhs_size", this._loadSidePanelSize().width + delta.width); window.localStorage.setItem("mx_rhs_size", (this.loadSidePanelSize().width + delta.width).toString());
}; };
_loadSidePanelSize() { private loadSidePanelSize(): {height: string | number, width: number} {
let rhsSize = parseInt(window.localStorage.getItem("mx_rhs_size"), 10); let rhsSize = parseInt(window.localStorage.getItem("mx_rhs_size"), 10);
if (isNaN(rhsSize)) { if (isNaN(rhsSize)) {
@ -47,7 +57,7 @@ export default class MainSplit extends React.Component {
}; };
} }
render() { public render(): JSX.Element {
const bodyView = React.Children.only(this.props.children); const bodyView = React.Children.only(this.props.children);
const panelView = this.props.panel; const panelView = this.props.panel;
@ -56,7 +66,7 @@ export default class MainSplit extends React.Component {
let children; let children;
if (hasResizer) { if (hasResizer) {
children = <Resizable children = <Resizable
defaultSize={this._loadSidePanelSize()} defaultSize={this.loadSidePanelSize()}
minWidth={264} minWidth={264}
maxWidth="50%" maxWidth="50%"
enable={{ enable={{
@ -69,9 +79,9 @@ export default class MainSplit extends React.Component {
bottomLeft: false, bottomLeft: false,
topLeft: false, topLeft: false,
}} }}
onResizeStart={this._onResizeStart} onResizeStart={this.onResizeStart}
onResize={this._onResize} onResize={this.onResize}
onResizeStop={this._onResizeStop} onResizeStop={this.onResizeStop}
className="mx_RightPanel_ResizeWrapper" className="mx_RightPanel_ResizeWrapper"
handleClasses={{ left: "mx_RightPanel_ResizeHandle" }} handleClasses={{ left: "mx_RightPanel_ResizeHandle" }}
> >

View file

@ -15,95 +15,110 @@ limitations under the License.
*/ */
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types';
import { _t, _td } from '../../languageHandler'; import { _t, _td } from '../../languageHandler';
import { MatrixClientPeg } from '../../MatrixClientPeg';
import Resend from '../../Resend'; import Resend from '../../Resend';
import dis from '../../dispatcher/dispatcher'; import dis from '../../dispatcher/dispatcher';
import { messageForResourceLimitError } from '../../utils/ErrorUtils'; import { messageForResourceLimitError } from '../../utils/ErrorUtils';
import { Action } from "../../dispatcher/actions"; import { Action } from "../../dispatcher/actions";
import { replaceableComponent } from "../../utils/replaceableComponent"; import { replaceableComponent } from "../../utils/replaceableComponent";
import { EventStatus } from "matrix-js-sdk/src/models/event"; import { EventStatus, MatrixEvent } from "matrix-js-sdk/src/models/event";
import NotificationBadge from "../views/rooms/NotificationBadge"; import NotificationBadge from "../views/rooms/NotificationBadge";
import { StaticNotificationState } from "../../stores/notifications/StaticNotificationState"; import { StaticNotificationState } from "../../stores/notifications/StaticNotificationState";
import AccessibleButton from "../views/elements/AccessibleButton"; import AccessibleButton from "../views/elements/AccessibleButton";
import InlineSpinner from "../views/elements/InlineSpinner"; import InlineSpinner from "../views/elements/InlineSpinner";
import { SyncState } from "matrix-js-sdk/src/sync.api";
import { ISyncStateData } from "matrix-js-sdk/src/sync";
import { Room } from "matrix-js-sdk/src/models/room";
import MatrixClientContext from "../../contexts/MatrixClientContext";
const STATUS_BAR_HIDDEN = 0; const STATUS_BAR_HIDDEN = 0;
const STATUS_BAR_EXPANDED = 1; const STATUS_BAR_EXPANDED = 1;
const STATUS_BAR_EXPANDED_LARGE = 2; const STATUS_BAR_EXPANDED_LARGE = 2;
export function getUnsentMessages(room) { export function getUnsentMessages(room: Room): MatrixEvent[] {
if (!room) { return []; } if (!room) { return []; }
return room.getPendingEvents().filter(function(ev) { return room.getPendingEvents().filter(function(ev) {
return ev.status === EventStatus.NOT_SENT; return ev.status === EventStatus.NOT_SENT;
}); });
} }
interface IProps {
// the room this statusbar is representing.
room: Room;
// true if the room is being peeked at. This affects components that shouldn't
// logically be shown when peeking, such as a prompt to invite people to a room.
isPeeking?: boolean;
// callback for when the user clicks on the 'resend all' button in the
// 'unsent messages' bar
onResendAllClick?: () => void;
// callback for when the user clicks on the 'cancel all' button in the
// 'unsent messages' bar
onCancelAllClick?: () => void;
// callback for when the user clicks on the 'invite others' button in the
// 'you are alone' bar
onInviteClick?: () => void;
// callback for when we do something that changes the size of the
// status bar. This is used to trigger a re-layout in the parent
// component.
onResize?: () => void;
// callback for when the status bar can be hidden from view, as it is
// not displaying anything
onHidden?: () => void;
// callback for when the status bar is displaying something and should
// be visible
onVisible?: () => void;
}
interface IState {
syncState: SyncState;
syncStateData: ISyncStateData;
unsentMessages: MatrixEvent[];
isResending: boolean;
}
@replaceableComponent("structures.RoomStatusBar") @replaceableComponent("structures.RoomStatusBar")
export default class RoomStatusBar extends React.PureComponent { export default class RoomStatusBar extends React.PureComponent<IProps, IState> {
static propTypes = { public static contextType = MatrixClientContext;
// the room this statusbar is representing.
room: PropTypes.object.isRequired,
// true if the room is being peeked at. This affects components that shouldn't constructor(props: IProps, context: typeof MatrixClientContext) {
// logically be shown when peeking, such as a prompt to invite people to a room. super(props, context);
isPeeking: PropTypes.bool,
// callback for when the user clicks on the 'resend all' button in the this.state = {
// 'unsent messages' bar syncState: this.context.getSyncState(),
onResendAllClick: PropTypes.func, syncStateData: this.context.getSyncStateData(),
unsentMessages: getUnsentMessages(this.props.room),
// callback for when the user clicks on the 'cancel all' button in the isResending: false,
// 'unsent messages' bar };
onCancelAllClick: PropTypes.func,
// callback for when the user clicks on the 'invite others' button in the
// 'you are alone' bar
onInviteClick: PropTypes.func,
// callback for when we do something that changes the size of the
// status bar. This is used to trigger a re-layout in the parent
// component.
onResize: PropTypes.func,
// callback for when the status bar can be hidden from view, as it is
// not displaying anything
onHidden: PropTypes.func,
// callback for when the status bar is displaying something and should
// be visible
onVisible: PropTypes.func,
};
state = {
syncState: MatrixClientPeg.get().getSyncState(),
syncStateData: MatrixClientPeg.get().getSyncStateData(),
unsentMessages: getUnsentMessages(this.props.room),
isResending: false,
};
componentDidMount() {
MatrixClientPeg.get().on("sync", this.onSyncStateChange);
MatrixClientPeg.get().on("Room.localEchoUpdated", this._onRoomLocalEchoUpdated);
this._checkSize();
} }
componentDidUpdate() { public componentDidMount(): void {
this._checkSize(); const client = this.context;
client.on("sync", this.onSyncStateChange);
client.on("Room.localEchoUpdated", this.onRoomLocalEchoUpdated);
this.checkSize();
} }
componentWillUnmount() { public componentDidUpdate(): void {
this.checkSize();
}
public componentWillUnmount(): void {
// we may have entirely lost our client as we're logging out before clicking login on the guest bar... // we may have entirely lost our client as we're logging out before clicking login on the guest bar...
const client = MatrixClientPeg.get(); const client = this.context;
if (client) { if (client) {
client.removeListener("sync", this.onSyncStateChange); client.removeListener("sync", this.onSyncStateChange);
client.removeListener("Room.localEchoUpdated", this._onRoomLocalEchoUpdated); client.removeListener("Room.localEchoUpdated", this.onRoomLocalEchoUpdated);
} }
} }
onSyncStateChange = (state, prevState, data) => { private onSyncStateChange = (state: SyncState, prevState: SyncState, data: ISyncStateData): void => {
if (state === "SYNCING" && prevState === "SYNCING") { if (state === "SYNCING" && prevState === "SYNCING") {
return; return;
} }
@ -113,7 +128,7 @@ export default class RoomStatusBar extends React.PureComponent {
}); });
}; };
_onResendAllClick = () => { private onResendAllClick = (): void => {
Resend.resendUnsentEvents(this.props.room).then(() => { Resend.resendUnsentEvents(this.props.room).then(() => {
this.setState({ isResending: false }); this.setState({ isResending: false });
}); });
@ -121,12 +136,12 @@ export default class RoomStatusBar extends React.PureComponent {
dis.fire(Action.FocusSendMessageComposer); dis.fire(Action.FocusSendMessageComposer);
}; };
_onCancelAllClick = () => { private onCancelAllClick = (): void => {
Resend.cancelUnsentEvents(this.props.room); Resend.cancelUnsentEvents(this.props.room);
dis.fire(Action.FocusSendMessageComposer); dis.fire(Action.FocusSendMessageComposer);
}; };
_onRoomLocalEchoUpdated = (event, room, oldEventId, oldStatus) => { private onRoomLocalEchoUpdated = (ev: MatrixEvent, room: Room) => {
if (room.roomId !== this.props.room.roomId) return; if (room.roomId !== this.props.room.roomId) return;
const messages = getUnsentMessages(this.props.room); const messages = getUnsentMessages(this.props.room);
this.setState({ this.setState({
@ -136,8 +151,8 @@ export default class RoomStatusBar extends React.PureComponent {
}; };
// Check whether current size is greater than 0, if yes call props.onVisible // Check whether current size is greater than 0, if yes call props.onVisible
_checkSize() { private checkSize(): void {
if (this._getSize()) { if (this.getSize()) {
if (this.props.onVisible) this.props.onVisible(); if (this.props.onVisible) this.props.onVisible();
} else { } else {
if (this.props.onHidden) this.props.onHidden(); if (this.props.onHidden) this.props.onHidden();
@ -147,8 +162,8 @@ export default class RoomStatusBar extends React.PureComponent {
// We don't need the actual height - just whether it is likely to have // We don't need the actual height - just whether it is likely to have
// changed - so we use '0' to indicate normal size, and other values to // changed - so we use '0' to indicate normal size, and other values to
// indicate other sizes. // indicate other sizes.
_getSize() { private getSize(): number {
if (this._shouldShowConnectionError()) { if (this.shouldShowConnectionError()) {
return STATUS_BAR_EXPANDED; return STATUS_BAR_EXPANDED;
} else if (this.state.unsentMessages.length > 0 || this.state.isResending) { } else if (this.state.unsentMessages.length > 0 || this.state.isResending) {
return STATUS_BAR_EXPANDED_LARGE; return STATUS_BAR_EXPANDED_LARGE;
@ -156,7 +171,7 @@ export default class RoomStatusBar extends React.PureComponent {
return STATUS_BAR_HIDDEN; return STATUS_BAR_HIDDEN;
} }
_shouldShowConnectionError() { private shouldShowConnectionError(): boolean {
// no conn bar trumps the "some not sent" msg since you can't resend without // no conn bar trumps the "some not sent" msg since you can't resend without
// a connection! // a connection!
// There's one situation in which we don't show this 'no connection' bar, and that's // There's one situation in which we don't show this 'no connection' bar, and that's
@ -164,12 +179,12 @@ export default class RoomStatusBar extends React.PureComponent {
const errorIsMauError = Boolean( const errorIsMauError = Boolean(
this.state.syncStateData && this.state.syncStateData &&
this.state.syncStateData.error && this.state.syncStateData.error &&
this.state.syncStateData.error.errcode === 'M_RESOURCE_LIMIT_EXCEEDED', this.state.syncStateData.error.name === 'M_RESOURCE_LIMIT_EXCEEDED',
); );
return this.state.syncState === "ERROR" && !errorIsMauError; return this.state.syncState === "ERROR" && !errorIsMauError;
} }
_getUnsentMessageContent() { private getUnsentMessageContent(): JSX.Element {
const unsentMessages = this.state.unsentMessages; const unsentMessages = this.state.unsentMessages;
let title; let title;
@ -221,10 +236,10 @@ export default class RoomStatusBar extends React.PureComponent {
} }
let buttonRow = <> let buttonRow = <>
<AccessibleButton onClick={this._onCancelAllClick} className="mx_RoomStatusBar_unsentCancelAllBtn"> <AccessibleButton onClick={this.onCancelAllClick} className="mx_RoomStatusBar_unsentCancelAllBtn">
{ _t("Delete all") } { _t("Delete all") }
</AccessibleButton> </AccessibleButton>
<AccessibleButton onClick={this._onResendAllClick} className="mx_RoomStatusBar_unsentResendAllBtn"> <AccessibleButton onClick={this.onResendAllClick} className="mx_RoomStatusBar_unsentResendAllBtn">
{ _t("Retry all") } { _t("Retry all") }
</AccessibleButton> </AccessibleButton>
</>; </>;
@ -260,8 +275,8 @@ export default class RoomStatusBar extends React.PureComponent {
</>; </>;
} }
render() { public render(): JSX.Element {
if (this._shouldShowConnectionError()) { if (this.shouldShowConnectionError()) {
return ( return (
<div className="mx_RoomStatusBar"> <div className="mx_RoomStatusBar">
<div role="alert"> <div role="alert">
@ -287,7 +302,7 @@ export default class RoomStatusBar extends React.PureComponent {
} }
if (this.state.unsentMessages.length > 0 || this.state.isResending) { if (this.state.unsentMessages.length > 0 || this.state.isResending) {
return this._getUnsentMessageContent(); return this.getUnsentMessageContent();
} }
return null; return null;

View file

@ -16,7 +16,6 @@ limitations under the License.
*/ */
import React, { createRef } from 'react'; import React, { createRef } from 'react';
import PropTypes from 'prop-types';
import { Key } from '../../Keyboard'; import { Key } from '../../Keyboard';
import dis from '../../dispatcher/dispatcher'; import dis from '../../dispatcher/dispatcher';
import { throttle } from 'lodash'; import { throttle } from 'lodash';
@ -24,106 +23,116 @@ import AccessibleButton from '../../components/views/elements/AccessibleButton';
import classNames from 'classnames'; import classNames from 'classnames';
import { replaceableComponent } from "../../utils/replaceableComponent"; import { replaceableComponent } from "../../utils/replaceableComponent";
interface IProps {
onSearch?: (query: string) => void;
onCleared?: (source?: string) => void;
onKeyDown?: (ev: React.KeyboardEvent) => void;
onFocus?: (ev: React.FocusEvent) => void;
onBlur?: (ev: React.FocusEvent) => void;
className?: string;
placeholder: string;
blurredPlaceholder?: string;
autoFocus?: boolean;
initialValue?: string;
collapsed?: boolean;
// If true, the search box will focus and clear itself
// on room search focus action (it would be nicer to take
// this functionality out, but not obvious how that would work)
enableRoomSearchFocus?: boolean;
}
interface IState {
searchTerm: string;
blurred: boolean;
}
@replaceableComponent("structures.SearchBox") @replaceableComponent("structures.SearchBox")
export default class SearchBox extends React.Component { export default class SearchBox extends React.Component<IProps, IState> {
static propTypes = { private dispatcherRef: string;
onSearch: PropTypes.func, private search = createRef<HTMLInputElement>();
onCleared: PropTypes.func,
onKeyDown: PropTypes.func,
className: PropTypes.string,
placeholder: PropTypes.string.isRequired,
autoFocus: PropTypes.bool,
initialValue: PropTypes.string,
// If true, the search box will focus and clear itself static defaultProps: Partial<IProps> = {
// on room search focus action (it would be nicer to take
// this functionality out, but not obvious how that would work)
enableRoomSearchFocus: PropTypes.bool,
};
static defaultProps = {
enableRoomSearchFocus: false, enableRoomSearchFocus: false,
}; };
constructor(props) { constructor(props: IProps) {
super(props); super(props);
this._search = createRef();
this.state = { this.state = {
searchTerm: this.props.initialValue || "", searchTerm: props.initialValue || "",
blurred: true, blurred: true,
}; };
} }
componentDidMount() { public componentDidMount(): void {
this.dispatcherRef = dis.register(this.onAction); this.dispatcherRef = dis.register(this.onAction);
} }
componentWillUnmount() { public componentWillUnmount(): void {
dis.unregister(this.dispatcherRef); dis.unregister(this.dispatcherRef);
} }
onAction = payload => { private onAction = (payload): void => {
if (!this.props.enableRoomSearchFocus) return; if (!this.props.enableRoomSearchFocus) return;
switch (payload.action) { switch (payload.action) {
case 'view_room': case 'view_room':
if (this._search.current && payload.clear_search) { if (this.search.current && payload.clear_search) {
this._clearSearch(); this.clearSearch();
} }
break; break;
case 'focus_room_filter': case 'focus_room_filter':
if (this._search.current) { if (this.search.current) {
this._search.current.focus(); this.search.current.focus();
} }
break; break;
} }
}; };
onChange = () => { private onChange = (): void => {
if (!this._search.current) return; if (!this.search.current) return;
this.setState({ searchTerm: this._search.current.value }); this.setState({ searchTerm: this.search.current.value });
this.onSearch(); this.onSearch();
}; };
onSearch = throttle(() => { private onSearch = throttle((): void => {
this.props.onSearch(this._search.current.value); this.props.onSearch(this.search.current.value);
}, 200, { trailing: true, leading: true }); }, 200, { trailing: true, leading: true });
_onKeyDown = ev => { private onKeyDown = (ev: React.KeyboardEvent): void => {
switch (ev.key) { switch (ev.key) {
case Key.ESCAPE: case Key.ESCAPE:
this._clearSearch("keyboard"); this.clearSearch("keyboard");
break; break;
} }
if (this.props.onKeyDown) this.props.onKeyDown(ev); if (this.props.onKeyDown) this.props.onKeyDown(ev);
}; };
_onFocus = ev => { private onFocus = (ev: React.FocusEvent): void => {
this.setState({ blurred: false }); this.setState({ blurred: false });
ev.target.select(); (ev.target as HTMLInputElement).select();
if (this.props.onFocus) { if (this.props.onFocus) {
this.props.onFocus(ev); this.props.onFocus(ev);
} }
}; };
_onBlur = ev => { private onBlur = (ev: React.FocusEvent): void => {
this.setState({ blurred: true }); this.setState({ blurred: true });
if (this.props.onBlur) { if (this.props.onBlur) {
this.props.onBlur(ev); this.props.onBlur(ev);
} }
}; };
_clearSearch(source) { private clearSearch(source?: string): void {
this._search.current.value = ""; this.search.current.value = "";
this.onChange(); this.onChange();
if (this.props.onCleared) { if (this.props.onCleared) {
this.props.onCleared(source); this.props.onCleared(source);
} }
} }
render() { public render(): JSX.Element {
// check for collapsed here and // check for collapsed here and
// not at parent so we keep // not at parent so we keep
// searchTerm in our state // searchTerm in our state
@ -136,7 +145,7 @@ export default class SearchBox extends React.Component {
key="button" key="button"
tabIndex={-1} tabIndex={-1}
className="mx_SearchBox_closeButton" className="mx_SearchBox_closeButton"
onClick={() => {this._clearSearch("button"); }} onClick={() => {this.clearSearch("button"); }}
/>) : undefined; />) : undefined;
// show a shorter placeholder when blurred, if requested // show a shorter placeholder when blurred, if requested
@ -151,13 +160,13 @@ export default class SearchBox extends React.Component {
<input <input
key="searchfield" key="searchfield"
type="text" type="text"
ref={this._search} ref={this.search}
className={"mx_textinput_icon mx_textinput_search " + className} className={"mx_textinput_icon mx_textinput_search " + className}
value={this.state.searchTerm} value={this.state.searchTerm}
onFocus={this._onFocus} onFocus={this.onFocus}
onChange={this.onChange} onChange={this.onChange}
onKeyDown={this._onKeyDown} onKeyDown={this.onKeyDown}
onBlur={this._onBlur} onBlur={this.onBlur}
placeholder={placeholder} placeholder={placeholder}
autoComplete="off" autoComplete="off"
autoFocus={this.props.autoFocus} autoFocus={this.props.autoFocus}

View file

@ -16,52 +16,60 @@ limitations under the License.
*/ */
import React from "react"; import React from "react";
import PropTypes from "prop-types";
import { MatrixClientPeg } from "../../MatrixClientPeg"; import { MatrixClientPeg } from "../../MatrixClientPeg";
import * as sdk from "../../index";
import Modal from '../../Modal'; import Modal from '../../Modal';
import { _t } from '../../languageHandler'; import { _t } from '../../languageHandler';
import HomePage from "./HomePage"; import HomePage from "./HomePage";
import { replaceableComponent } from "../../utils/replaceableComponent"; import { replaceableComponent } from "../../utils/replaceableComponent";
import { MatrixEvent } from "matrix-js-sdk/src/models/event"; import { MatrixEvent } from "matrix-js-sdk/src/models/event";
import { RoomMember } from "matrix-js-sdk/src/models/room-member"; import { RoomMember } from "matrix-js-sdk/src/models/room-member";
import ErrorDialog from "../views/dialogs/ErrorDialog";
import MainSplit from "./MainSplit";
import RightPanel from "./RightPanel";
import Spinner from "../views/elements/Spinner";
import ResizeNotifier from "../../utils/ResizeNotifier";
interface IProps {
userId?: string;
resizeNotifier: ResizeNotifier;
}
interface IState {
loading: boolean;
member?: RoomMember;
}
@replaceableComponent("structures.UserView") @replaceableComponent("structures.UserView")
export default class UserView extends React.Component { export default class UserView extends React.Component<IProps, IState> {
static get propTypes() { constructor(props: IProps) {
return { super(props);
userId: PropTypes.string, this.state = {
loading: true,
}; };
} }
constructor(props) { public componentDidMount(): void {
super(props);
this.state = {};
}
componentDidMount() {
if (this.props.userId) { if (this.props.userId) {
this._loadProfileInfo(); this.loadProfileInfo();
} }
} }
componentDidUpdate(prevProps) { public componentDidUpdate(prevProps: IProps): void {
// XXX: We shouldn't need to null check the userId here, but we declare // XXX: We shouldn't need to null check the userId here, but we declare
// it as optional and MatrixChat sometimes fires in a way which results // it as optional and MatrixChat sometimes fires in a way which results
// in an NPE when we try to update the profile info. // in an NPE when we try to update the profile info.
if (prevProps.userId !== this.props.userId && this.props.userId) { if (prevProps.userId !== this.props.userId && this.props.userId) {
this._loadProfileInfo(); this.loadProfileInfo();
} }
} }
async _loadProfileInfo() { private async loadProfileInfo(): Promise<void> {
const cli = MatrixClientPeg.get(); const cli = MatrixClientPeg.get();
this.setState({ loading: true }); this.setState({ loading: true });
let profileInfo; let profileInfo;
try { try {
profileInfo = await cli.getProfileInfo(this.props.userId); profileInfo = await cli.getProfileInfo(this.props.userId);
} catch (err) { } catch (err) {
const ErrorDialog = sdk.getComponent('dialogs.ErrorDialog');
Modal.createTrackedDialog(_t('Could not load user profile'), '', ErrorDialog, { Modal.createTrackedDialog(_t('Could not load user profile'), '', ErrorDialog, {
title: _t('Could not load user profile'), title: _t('Could not load user profile'),
description: ((err && err.message) ? err.message : _t("Operation failed")), description: ((err && err.message) ? err.message : _t("Operation failed")),
@ -75,14 +83,11 @@ export default class UserView extends React.Component {
this.setState({ member, loading: false }); this.setState({ member, loading: false });
} }
render() { public render(): JSX.Element {
if (this.state.loading) { if (this.state.loading) {
const Spinner = sdk.getComponent("elements.Spinner");
return <Spinner />; return <Spinner />;
} else if (this.state.member) { } else if (this.state.member?.user) {
const RightPanel = sdk.getComponent('structures.RightPanel'); const panel = <RightPanel user={this.state.member.user} resizeNotifier={this.props.resizeNotifier} />;
const MainSplit = sdk.getComponent('structures.MainSplit');
const panel = <RightPanel user={this.state.member} />;
return (<MainSplit panel={panel} resizeNotifier={this.props.resizeNotifier}> return (<MainSplit panel={panel} resizeNotifier={this.props.resizeNotifier}>
<HomePage /> <HomePage />
</MainSplit>); </MainSplit>);

View file

@ -17,24 +17,28 @@ limitations under the License.
*/ */
import React from "react"; import React from "react";
import PropTypes from "prop-types";
import SyntaxHighlight from "../views/elements/SyntaxHighlight"; import SyntaxHighlight from "../views/elements/SyntaxHighlight";
import { _t } from "../../languageHandler"; import { _t } from "../../languageHandler";
import * as sdk from "../../index";
import MatrixClientContext from "../../contexts/MatrixClientContext"; import MatrixClientContext from "../../contexts/MatrixClientContext";
import { SendCustomEvent } from "../views/dialogs/DevtoolsDialog"; import { SendCustomEvent } from "../views/dialogs/DevtoolsDialog";
import { canEditContent } from "../../utils/EventUtils"; import { canEditContent } from "../../utils/EventUtils";
import { MatrixClientPeg } from '../../MatrixClientPeg'; import { MatrixClientPeg } from '../../MatrixClientPeg';
import { replaceableComponent } from "../../utils/replaceableComponent"; import { replaceableComponent } from "../../utils/replaceableComponent";
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
import { IDialogProps } from "../views/dialogs/IDialogProps";
import BaseDialog from "../views/dialogs/BaseDialog";
interface IProps extends IDialogProps {
mxEvent: MatrixEvent; // the MatrixEvent associated with the context menu
}
interface IState {
isEditing: boolean;
}
@replaceableComponent("structures.ViewSource") @replaceableComponent("structures.ViewSource")
export default class ViewSource extends React.Component { export default class ViewSource extends React.Component<IProps, IState> {
static propTypes = { constructor(props: IProps) {
onFinished: PropTypes.func.isRequired,
mxEvent: PropTypes.object.isRequired, // the MatrixEvent associated with the context menu
};
constructor(props) {
super(props); super(props);
this.state = { this.state = {
@ -42,19 +46,20 @@ export default class ViewSource extends React.Component {
}; };
} }
onBack() { private onBack(): void {
// TODO: refresh the "Event ID:" modal header // TODO: refresh the "Event ID:" modal header
this.setState({ isEditing: false }); this.setState({ isEditing: false });
} }
onEdit() { private onEdit(): void {
this.setState({ isEditing: true }); this.setState({ isEditing: true });
} }
// returns the dialog body for viewing the event source // returns the dialog body for viewing the event source
viewSourceContent() { private viewSourceContent(): JSX.Element {
const mxEvent = this.props.mxEvent.replacingEvent() || this.props.mxEvent; // show the replacing event, not the original, if it is an edit const mxEvent = this.props.mxEvent.replacingEvent() || this.props.mxEvent; // show the replacing event, not the original, if it is an edit
const isEncrypted = mxEvent.isEncrypted(); const isEncrypted = mxEvent.isEncrypted();
// @ts-ignore
const decryptedEventSource = mxEvent.clearEvent; // FIXME: clearEvent is private const decryptedEventSource = mxEvent.clearEvent; // FIXME: clearEvent is private
const originalEventSource = mxEvent.event; const originalEventSource = mxEvent.event;
@ -86,7 +91,7 @@ export default class ViewSource extends React.Component {
} }
// returns the id of the initial message, not the id of the previous edit // returns the id of the initial message, not the id of the previous edit
getBaseEventId() { private getBaseEventId(): string {
const mxEvent = this.props.mxEvent.replacingEvent() || this.props.mxEvent; // show the replacing event, not the original, if it is an edit const mxEvent = this.props.mxEvent.replacingEvent() || this.props.mxEvent; // show the replacing event, not the original, if it is an edit
const isEncrypted = mxEvent.isEncrypted(); const isEncrypted = mxEvent.isEncrypted();
const baseMxEvent = this.props.mxEvent; const baseMxEvent = this.props.mxEvent;
@ -100,7 +105,7 @@ export default class ViewSource extends React.Component {
} }
// returns the SendCustomEvent component prefilled with the correct details // returns the SendCustomEvent component prefilled with the correct details
editSourceContent() { private editSourceContent(): JSX.Element {
const mxEvent = this.props.mxEvent.replacingEvent() || this.props.mxEvent; // show the replacing event, not the original, if it is an edit const mxEvent = this.props.mxEvent.replacingEvent() || this.props.mxEvent; // show the replacing event, not the original, if it is an edit
const isStateEvent = mxEvent.isState(); const isStateEvent = mxEvent.isState();
@ -159,14 +164,13 @@ export default class ViewSource extends React.Component {
} }
} }
canSendStateEvent(mxEvent) { private canSendStateEvent(mxEvent: MatrixEvent): boolean {
const cli = MatrixClientPeg.get(); const cli = MatrixClientPeg.get();
const room = cli.getRoom(mxEvent.getRoomId()); const room = cli.getRoom(mxEvent.getRoomId());
return room.currentState.mayClientSendStateEvent(mxEvent.getType(), cli); return room.currentState.mayClientSendStateEvent(mxEvent.getType(), cli);
} }
render() { public render(): JSX.Element {
const BaseDialog = sdk.getComponent("views.dialogs.BaseDialog");
const mxEvent = this.props.mxEvent.replacingEvent() || this.props.mxEvent; // show the replacing event, not the original, if it is an edit const mxEvent = this.props.mxEvent.replacingEvent() || this.props.mxEvent; // show the replacing event, not the original, if it is an edit
const isEditing = this.state.isEditing; const isEditing = this.state.isEditing;

View file

@ -258,7 +258,6 @@ export const AddExistingToSpace: React.FC<IAddExistingToSpaceProps> = ({
className="mx_textinput_icon mx_textinput_search" className="mx_textinput_icon mx_textinput_search"
placeholder={filterPlaceholder} placeholder={filterPlaceholder}
onSearch={setQuery} onSearch={setQuery}
autoComplete={true}
autoFocus={true} autoFocus={true}
/> />
<AutoHideScrollbar className="mx_AddExistingToSpace_content"> <AutoHideScrollbar className="mx_AddExistingToSpace_content">

View file

@ -243,7 +243,6 @@ const ForwardDialog: React.FC<IProps> = ({ matrixClient: cli, event, permalinkCr
className="mx_textinput_icon mx_textinput_search" className="mx_textinput_icon mx_textinput_search"
placeholder={_t("Search for rooms or people")} placeholder={_t("Search for rooms or people")}
onSearch={setQuery} onSearch={setQuery}
autoComplete={true}
autoFocus={true} autoFocus={true}
/> />
<AutoHideScrollbar className="mx_ForwardList_content"> <AutoHideScrollbar className="mx_ForwardList_content">

View file

@ -57,7 +57,6 @@ const SpaceChildPicker = ({ filterPlaceholder, rooms, selected, onChange }) => {
className="mx_textinput_icon mx_textinput_search" className="mx_textinput_icon mx_textinput_search"
placeholder={filterPlaceholder} placeholder={filterPlaceholder}
onSearch={setQuery} onSearch={setQuery}
autoComplete={true}
autoFocus={true} autoFocus={true}
/> />
<AutoHideScrollbar className="mx_LeaveSpaceDialog_content"> <AutoHideScrollbar className="mx_LeaveSpaceDialog_content">

View file

@ -126,7 +126,6 @@ const ManageRestrictedJoinRuleDialog: React.FC<IProps> = ({ room, selected = [],
className="mx_textinput_icon mx_textinput_search" className="mx_textinput_icon mx_textinput_search"
placeholder={_t("Search spaces")} placeholder={_t("Search spaces")}
onSearch={setQuery} onSearch={setQuery}
autoComplete={true}
autoFocus={true} autoFocus={true}
/> />
<AutoHideScrollbar className="mx_ManageRestrictedJoinRuleDialog_content"> <AutoHideScrollbar className="mx_ManageRestrictedJoinRuleDialog_content">