Merge remote-tracking branch 'upstream/develop' into feature/collapse-pinned-mels/17938

This commit is contained in:
Šimon Brandner 2021-07-22 07:51:58 +02:00
commit 2df4f7b859
No known key found for this signature in database
GPG key ID: 55C211A1226CB17D
483 changed files with 11911 additions and 7811 deletions

View file

@ -0,0 +1,145 @@
/*
Copyright 2021 Šimon Brandner <simon.bra.ag@gmail.com>
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 { EventType } from "matrix-js-sdk/src/@types/event";
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
import { CallEvent, CallState, CallType, MatrixCall } from "matrix-js-sdk/src/webrtc/call";
import CallHandler, { CallHandlerEvent } from '../../CallHandler';
import { EventEmitter } from 'events';
import { MatrixClientPeg } from "../../MatrixClientPeg";
import defaultDispatcher from "../../dispatcher/dispatcher";
export enum CallEventGrouperEvent {
StateChanged = "state_changed",
SilencedChanged = "silenced_changed",
}
const SUPPORTED_STATES = [
CallState.Connected,
CallState.Connecting,
CallState.Ringing,
];
export enum CustomCallState {
Missed = "missed",
}
export default class CallEventGrouper extends EventEmitter {
private events: Set<MatrixEvent> = new Set<MatrixEvent>();
private call: MatrixCall;
public state: CallState | CustomCallState;
constructor() {
super();
CallHandler.sharedInstance().addListener(CallHandlerEvent.CallsChanged, this.setCall);
CallHandler.sharedInstance().addListener(CallHandlerEvent.SilencedCallsChanged, this.onSilencedCallsChanged);
}
private get invite(): MatrixEvent {
return [...this.events].find((event) => event.getType() === EventType.CallInvite);
}
private get hangup(): MatrixEvent {
return [...this.events].find((event) => event.getType() === EventType.CallHangup);
}
private get reject(): MatrixEvent {
return [...this.events].find((event) => event.getType() === EventType.CallReject);
}
public get isVoice(): boolean {
const invite = this.invite;
if (!invite) return;
// FIXME: Find a better way to determine this from the event?
if (invite.getContent()?.offer?.sdp?.indexOf('m=video') !== -1) return false;
return true;
}
public get hangupReason(): string | null {
return this.hangup?.getContent()?.reason;
}
/**
* Returns true if there are only events from the other side - we missed the call
*/
private get callWasMissed(): boolean {
return ![...this.events].some((event) => event.sender?.userId === MatrixClientPeg.get().getUserId());
}
private get callId(): string {
return [...this.events][0].getContent().call_id;
}
private onSilencedCallsChanged = () => {
const newState = CallHandler.sharedInstance().isCallSilenced(this.callId);
this.emit(CallEventGrouperEvent.SilencedChanged, newState);
};
public answerCall = () => {
this.call?.answer();
};
public rejectCall = () => {
this.call?.reject();
};
public callBack = () => {
defaultDispatcher.dispatch({
action: 'place_call',
type: this.isVoice ? CallType.Voice : CallType.Video,
room_id: [...this.events][0]?.getRoomId(),
});
};
public toggleSilenced = () => {
const silenced = CallHandler.sharedInstance().isCallSilenced(this.callId);
silenced ?
CallHandler.sharedInstance().unSilenceCall(this.callId) :
CallHandler.sharedInstance().silenceCall(this.callId);
};
private setCallListeners() {
if (!this.call) return;
this.call.addListener(CallEvent.State, this.setState);
}
private setState = () => {
if (SUPPORTED_STATES.includes(this.call?.state)) {
this.state = this.call.state;
} else {
if (this.callWasMissed) this.state = CustomCallState.Missed;
else if (this.reject) this.state = CallState.Ended;
else if (this.hangup) this.state = CallState.Ended;
else if (this.invite && this.call) this.state = CallState.Connecting;
}
this.emit(CallEventGrouperEvent.StateChanged, this.state);
};
private setCall = () => {
if (this.call) return;
this.call = CallHandler.sharedInstance().getCallById(this.callId);
this.setCallListeners();
this.setState();
};
public add(event: MatrixEvent) {
this.events.add(event);
this.setCall();
}
}

View file

@ -56,7 +56,7 @@ class CustomRoomTagPanel extends React.Component {
return (<div className={classes}>
<div className="mx_CustomRoomTagPanel_divider" />
<AutoHideScrollbar className="mx_CustomRoomTagPanel_scroller">
{tags}
{ tags }
</AutoHideScrollbar>
</div>);
}
@ -84,7 +84,7 @@ class CustomRoomTagTile extends React.Component {
"mx_TagTile_badge": true,
"mx_TagTile_badgeHighlight": badgeNotifState.hasMentions,
});
badgeElement = (<div className={badgeClasses}>{FormattingUtils.formatCount(badgeNotifState.count)}</div>);
badgeElement = (<div className={badgeClasses}>{ FormattingUtils.formatCount(badgeNotifState.count) }</div>);
}
return (

View file

@ -125,11 +125,11 @@ export default class EmbeddedPage extends React.PureComponent {
if (this.props.scrollbar) {
return <AutoHideScrollbar className={classes}>
{content}
{ content }
</AutoHideScrollbar>;
} else {
return <div className={classes}>
{content}
{ content }
</div>;
}
}

View file

@ -241,8 +241,8 @@ class FilePanel extends React.Component<IProps, IState> {
// wrap a TimelinePanel with the jump-to-event bits turned off.
const emptyState = (<div className="mx_RightPanel_empty mx_FilePanel_empty">
<h2>{_t('No files visible in this room')}</h2>
<p>{_t('Attach files from chat or just drag and drop them anywhere in a room.')}</p>
<h2>{ _t('No files visible in this room') }</h2>
<p>{ _t('Attach files from chat or just drag and drop them anywhere in a room.') }</p>
</div>);
const isRoomEncrypted = this.noRoom ? false : MatrixClientPeg.get().isRoomEncrypted(this.props.roomId);
@ -262,7 +262,7 @@ class FilePanel extends React.Component<IProps, IState> {
manageReadReceipts={false}
manageReadMarkers={false}
timelineSet={this.state.timelineSet}
showUrlPreview = {false}
showUrlPreview={false}
onPaginationRequest={this.onPaginationRequest}
tileShape={TileShape.FileGrid}
resizeNotifier={this.props.resizeNotifier}

View file

@ -28,8 +28,8 @@ export default class GenericErrorPage extends React.PureComponent {
render() {
return <div className='mx_GenericErrorPage'>
<div className='mx_GenericErrorPage_box'>
<h1>{this.props.title}</h1>
<p>{this.props.message}</p>
<h1>{ this.props.title }</h1>
<p>{ this.props.message }</p>
</div>
</div>;
}

View file

@ -819,12 +819,12 @@ export default class GroupView extends React.Component {
let hostingSignup = null;
if (hostingSignupLink && this.state.isUserPrivileged) {
hostingSignup = <div className="mx_GroupView_hostingSignup">
{_t(
{ _t(
"Want more than a community? <a>Get your own server</a>", {},
{
a: sub => <a href={hostingSignupLink} target="_blank" rel="noreferrer noopener">{sub}</a>,
a: sub => <a href={hostingSignupLink} target="_blank" rel="noreferrer noopener">{ sub }</a>,
},
)}
) }
<a href={hostingSignupLink} target="_blank" rel="noreferrer noopener">
<img src={require("../../../res/img/external-link.svg")} width="11" height="10" alt='' />
</a>

View file

@ -54,7 +54,7 @@ export default class InteractiveAuthComponent extends React.Component {
// * emailSid {string} If email auth was performed, the sid of
// the auth session.
// * clientSecret {string} The client secret used in auth
// sessions with the ID server.
// sessions with the identity server.
onAuthFinished: PropTypes.func.isRequired,
// Inputs provided by the user to the auth process

View file

@ -429,7 +429,7 @@ export default class LeftPanel extends React.Component<IProps, IState> {
onSelectRoom={this.selectRoom}
/>
{dialPadButton}
{ dialPadButton }
<AccessibleTooltipButton
className={classNames("mx_LeftPanel_exploreButton", {
@ -448,7 +448,7 @@ export default class LeftPanel extends React.Component<IProps, IState> {
leftLeftPanel = (
<div className="mx_LeftPanel_GroupFilterPanelContainer">
<GroupFilterPanel />
{SettingsStore.getValue("feature_custom_tags") ? <CustomRoomTagPanel /> : null}
{ SettingsStore.getValue("feature_custom_tags") ? <CustomRoomTagPanel /> : null }
</div>
);
}
@ -476,11 +476,11 @@ export default class LeftPanel extends React.Component<IProps, IState> {
return (
<div className={containerClasses} ref={this.ref}>
{leftLeftPanel}
{ leftLeftPanel }
<aside className="mx_LeftPanel_roomListContainer">
{this.renderHeader()}
{this.renderSearchDialExplore()}
{this.renderBreadcrumbs()}
{ this.renderHeader() }
{ this.renderSearchDialExplore() }
{ this.renderBreadcrumbs() }
<RoomListNumResults onVisibilityChange={this.refreshStickyHeaders} />
<div className="mx_LeftPanel_roomListWrapper">
<div
@ -490,7 +490,7 @@ export default class LeftPanel extends React.Component<IProps, IState> {
// overflow:scroll;, so force it out of tab order.
tabIndex={-1}
>
{roomList}
{ roomList }
</div>
</div>
{ !this.props.isMinimized && <LeftPanelWidget /> }

View file

@ -125,15 +125,15 @@ const LeftPanelWidget: React.FC = () => {
<span>{ WidgetUtils.getWidgetName(app) }</span>
</AccessibleButton>
{/* Code for the maximise button for once we have full screen widgets */}
{/*<AccessibleTooltipButton
{ /* Code for the maximise button for once we have full screen widgets */ }
{ /*<AccessibleTooltipButton
tabIndex={tabIndex}
onClick={() => {
}}
className="mx_LeftPanelWidget_maximizeButton"
tooltipClassName="mx_LeftPanelWidget_maximizeButtonTooltip"
title={_t("Maximize")}
/>*/}
/>*/ }
</div>
</div>

View file

@ -17,8 +17,8 @@ limitations under the License.
*/
import * as React from 'react';
import * as PropTypes from 'prop-types';
import { MatrixClient } from 'matrix-js-sdk/src/client';
import { MatrixEvent } from 'matrix-js-sdk/src/models/event';
import { Key } from '../../Keyboard';
import PageTypes from '../../PageTypes';
@ -63,6 +63,7 @@ import ToastContainer from './ToastContainer';
import MyGroups from "./MyGroups";
import UserView from "./UserView";
import GroupView from "./GroupView";
import SpaceStore from "../../stores/SpaceStore";
// We need to fetch each pinned message individually (if we don't already have it)
// so each pinned message may trigger a request. Limit the number per room for sanity.
@ -78,6 +79,8 @@ function canElementReceiveInput(el) {
interface IProps {
matrixClient: MatrixClient;
// Called with the credentials of a registered user (if they were a ROU that
// transitioned to PWLU)
onRegistered: (credentials: IMatrixClientCreds) => Promise<MatrixClient>;
hideToSRUsers: boolean;
resizeNotifier: ResizeNotifier;
@ -139,18 +142,6 @@ interface IState {
class LoggedInView extends React.Component<IProps, IState> {
static displayName = 'LoggedInView';
static propTypes = {
matrixClient: PropTypes.instanceOf(MatrixClient).isRequired,
page_type: PropTypes.string.isRequired,
onRoomCreated: PropTypes.func,
// Called with the credentials of a registered user (if they were a ROU that
// transitioned to PWLU)
onRegistered: PropTypes.func,
// and lots and lots of other stuff.
};
protected readonly _matrixClient: MatrixClient;
protected readonly _roomView: React.RefObject<any>;
protected readonly _resizeContainer: React.RefObject<ResizeHandle>;
@ -180,10 +171,10 @@ class LoggedInView extends React.Component<IProps, IState> {
}
componentDidMount() {
document.addEventListener('keydown', this._onNativeKeyDown, false);
document.addEventListener('keydown', this.onNativeKeyDown, false);
CallHandler.sharedInstance().addListener(CallHandlerEvent.CallsChanged, this.onCallsChanged);
this._updateServerNoticeEvents();
this.updateServerNoticeEvents();
this._matrixClient.on("accountData", this.onAccountData);
this._matrixClient.on("sync", this.onSync);
@ -199,13 +190,13 @@ class LoggedInView extends React.Component<IProps, IState> {
"useCompactLayout", null, this.onCompactLayoutChanged,
);
this.resizer = this._createResizer();
this.resizer = this.createResizer();
this.resizer.attach();
this._loadResizerPreferences();
this.loadResizerPreferences();
}
componentWillUnmount() {
document.removeEventListener('keydown', this._onNativeKeyDown, false);
document.removeEventListener('keydown', this.onNativeKeyDown, false);
CallHandler.sharedInstance().removeListener(CallHandlerEvent.CallsChanged, this.onCallsChanged);
this._matrixClient.removeListener("accountData", this.onAccountData);
this._matrixClient.removeListener("sync", this.onSync);
@ -220,37 +211,37 @@ class LoggedInView extends React.Component<IProps, IState> {
});
};
canResetTimelineInRoom = (roomId) => {
public canResetTimelineInRoom = (roomId: string) => {
if (!this._roomView.current) {
return true;
}
return this._roomView.current.canResetTimeline();
};
_createResizer() {
let size;
let collapsed;
private createResizer() {
let panelSize;
let panelCollapsed;
const collapseConfig: ICollapseConfig = {
// TODO decrease this once Spaces launches as it'll no longer need to include the 56px Community Panel
toggleSize: 206 - 50,
onCollapsed: (_collapsed) => {
collapsed = _collapsed;
if (_collapsed) {
onCollapsed: (collapsed) => {
panelCollapsed = collapsed;
if (collapsed) {
dis.dispatch({ action: "hide_left_panel" });
window.localStorage.setItem("mx_lhs_size", '0');
} else {
dis.dispatch({ action: "show_left_panel" });
}
},
onResized: (_size) => {
size = _size;
onResized: (size) => {
panelSize = size;
this.props.resizeNotifier.notifyLeftHandleResized();
},
onResizeStart: () => {
this.props.resizeNotifier.startResizing();
},
onResizeStop: () => {
if (!collapsed) window.localStorage.setItem("mx_lhs_size", '' + size);
if (!panelCollapsed) window.localStorage.setItem("mx_lhs_size", '' + panelSize);
this.props.resizeNotifier.stopResizing();
},
isItemCollapsed: domNode => {
@ -266,7 +257,7 @@ class LoggedInView extends React.Component<IProps, IState> {
return resizer;
}
_loadResizerPreferences() {
private loadResizerPreferences() {
let lhsSize = parseInt(window.localStorage.getItem("mx_lhs_size"), 10);
if (isNaN(lhsSize)) {
lhsSize = 350;
@ -274,7 +265,7 @@ class LoggedInView extends React.Component<IProps, IState> {
this.resizer.forHandleAt(0).resize(lhsSize);
}
onAccountData = (event) => {
private onAccountData = (event: MatrixEvent) => {
if (event.getType() === "m.ignored_user_list") {
dis.dispatch({ action: "ignore_state_changed" });
}
@ -306,16 +297,16 @@ class LoggedInView extends React.Component<IProps, IState> {
}
if (oldSyncState === 'PREPARED' && syncState === 'SYNCING') {
this._updateServerNoticeEvents();
this.updateServerNoticeEvents();
} else {
this._calculateServerLimitToast(this.state.syncErrorData, this.state.usageLimitEventContent);
this.calculateServerLimitToast(this.state.syncErrorData, this.state.usageLimitEventContent);
}
};
onRoomStateEvents = (ev, state) => {
const serverNoticeList = RoomListStore.instance.orderedLists[DefaultTagID.ServerNotice];
if (serverNoticeList && serverNoticeList.some(r => r.roomId === ev.getRoomId())) {
this._updateServerNoticeEvents();
this.updateServerNoticeEvents();
}
};
@ -325,7 +316,7 @@ class LoggedInView extends React.Component<IProps, IState> {
});
};
_calculateServerLimitToast(syncError: IState["syncErrorData"], usageLimitEventContent?: IUsageLimit) {
private calculateServerLimitToast(syncError: IState["syncErrorData"], usageLimitEventContent?: IUsageLimit) {
const error = syncError && syncError.error && syncError.error.errcode === "M_RESOURCE_LIMIT_EXCEEDED";
if (error) {
usageLimitEventContent = syncError.error.data;
@ -345,7 +336,7 @@ class LoggedInView extends React.Component<IProps, IState> {
}
}
_updateServerNoticeEvents = async () => {
private updateServerNoticeEvents = async () => {
const serverNoticeList = RoomListStore.instance.orderedLists[DefaultTagID.ServerNotice];
if (!serverNoticeList) return [];
@ -377,7 +368,7 @@ class LoggedInView extends React.Component<IProps, IState> {
);
});
const usageLimitEventContent = usageLimitEvent && usageLimitEvent.getContent();
this._calculateServerLimitToast(this.state.syncErrorData, usageLimitEventContent);
this.calculateServerLimitToast(this.state.syncErrorData, usageLimitEventContent);
this.setState({
usageLimitEventContent,
usageLimitEventTs: pinnedEventTs,
@ -386,7 +377,7 @@ class LoggedInView extends React.Component<IProps, IState> {
});
};
_onPaste = (ev) => {
private onPaste = (ev) => {
let canReceiveInput = false;
let element = ev.target;
// test for all parents because the target can be a child of a contenteditable element
@ -424,22 +415,22 @@ class LoggedInView extends React.Component<IProps, IState> {
We also listen with a native listener on the document to get keydown events when no element is focused.
Bubbling is irrelevant here as the target is the body element.
*/
_onReactKeyDown = (ev) => {
private onReactKeyDown = (ev) => {
// events caught while bubbling up on the root element
// of this component, so something must be focused.
this._onKeyDown(ev);
this.onKeyDown(ev);
};
_onNativeKeyDown = (ev) => {
private onNativeKeyDown = (ev) => {
// only pass this if there is no focused element.
// if there is, _onKeyDown will be called by the
// if there is, onKeyDown will be called by the
// react keydown handler that respects the react bubbling order.
if (ev.target === document.body) {
this._onKeyDown(ev);
this.onKeyDown(ev);
}
};
_onKeyDown = (ev) => {
private onKeyDown = (ev) => {
let handled = false;
const roomAction = getKeyBindingsManager().getRoomAction(ev);
@ -449,7 +440,7 @@ class LoggedInView extends React.Component<IProps, IState> {
case RoomAction.JumpToFirstMessage:
case RoomAction.JumpToLatestMessage:
// pass the event down to the scroll panel
this._onScrollKeyPressed(ev);
this.onScrollKeyPressed(ev);
handled = true;
break;
case RoomAction.FocusSearch:
@ -564,7 +555,7 @@ class LoggedInView extends React.Component<IProps, IState> {
* dispatch a page-up/page-down/etc to the appropriate component
* @param {Object} ev The key event
*/
_onScrollKeyPressed = (ev) => {
private onScrollKeyPressed = (ev) => {
if (this._roomView.current) {
this._roomView.current.handleScrollKey(ev);
}
@ -624,14 +615,14 @@ class LoggedInView extends React.Component<IProps, IState> {
return (
<MatrixClientContext.Provider value={this._matrixClient}>
<div
onPaste={this._onPaste}
onKeyDown={this._onReactKeyDown}
onPaste={this.onPaste}
onKeyDown={this.onReactKeyDown}
className='mx_MatrixChat_wrapper'
aria-hidden={this.props.hideToSRUsers}
>
<ToastContainer />
<div ref={this._resizeContainer} className={bodyClasses}>
{ SettingsStore.getValue("feature_spaces") ? <SpacePanel /> : null }
{ SpaceStore.spacesEnabled ? <SpacePanel /> : null }
<LeftPanel
isMinimized={this.props.collapseLhs || false}
resizeNotifier={this.props.resizeNotifier}
@ -643,7 +634,7 @@ class LoggedInView extends React.Component<IProps, IState> {
<CallContainer />
<NonUrgentToastContainer />
<HostSignupContainer />
{audioFeedArraysForCalls}
{ audioFeedArraysForCalls }
</MatrixClientContext.Provider>
);
}

View file

@ -19,7 +19,7 @@ import { createClient } from "matrix-js-sdk/src/matrix";
import { InvalidStoreError } from "matrix-js-sdk/src/errors";
import { RoomMember } from "matrix-js-sdk/src/models/room-member";
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
import { sleep, defer, IDeferred } from "matrix-js-sdk/src/utils";
import { sleep, defer, IDeferred, QueryDict } from "matrix-js-sdk/src/utils";
// focus-visible is a Polyfill for the :focus-visible CSS pseudo-attribute used by _AccessibleButton.scss
import 'focus-visible';
@ -105,6 +105,8 @@ import VerificationRequestToast from '../views/toasts/VerificationRequestToast';
import PerformanceMonitor, { PerformanceEntryNames } from "../../performance";
import UIStore, { UI_EVENTS } from "../../stores/UIStore";
import SoftLogout from './auth/SoftLogout';
import { makeRoomPermalink } from "../../utils/permalinks/Permalinks";
import { copyPlaintext } from "../../utils/strings";
/** constants for MatrixChat.state.view */
export enum Views {
@ -153,7 +155,7 @@ const ONBOARDING_FLOW_STARTERS = [
interface IScreen {
screen: string;
params?: object;
params?: QueryDict;
}
/* eslint-disable camelcase */
@ -183,9 +185,9 @@ interface IProps { // TODO type things better
onNewScreen: (screen: string, replaceLast: boolean) => void;
enableGuest?: boolean;
// the queryParams extracted from the [real] query-string of the URI
realQueryParams?: Record<string, string>;
realQueryParams?: QueryDict;
// the initial queryParams extracted from the hash-fragment of the URI
startingFragmentQueryParams?: Record<string, string>;
startingFragmentQueryParams?: QueryDict;
// called when we have completed a token login
onTokenLoginCompleted?: () => void;
// Represents the screen to display as a result of parsing the initial window.location
@ -193,7 +195,7 @@ interface IProps { // TODO type things better
// displayname, if any, to set on the device when logging in/registering.
defaultDeviceDisplayName?: string;
// A function that makes a registration URL
makeRegistrationUrl: (object) => string;
makeRegistrationUrl: (params: QueryDict) => string;
}
interface IState {
@ -251,7 +253,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
private pageChanging: boolean;
private tokenLogin?: boolean;
private accountPassword?: string;
private accountPasswordTimer?: NodeJS.Timeout;
private accountPasswordTimer?: number;
private focusComposer: boolean;
private subTitleStatus: string;
private prevWindowWidth: number;
@ -296,7 +298,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
if (this.screenAfterLogin.screen.startsWith("room/") && params['signurl'] && params['email']) {
// probably a threepid invite - try to store it
const roomId = this.screenAfterLogin.screen.substring("room/".length);
ThreepidInviteStore.instance.storeInvite(roomId, params as IThreepidInviteWireFormat);
ThreepidInviteStore.instance.storeInvite(roomId, params as unknown as IThreepidInviteWireFormat);
}
}
@ -429,7 +431,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
}
// TODO: [REACT-WARNING] Replace with appropriate lifecycle stage
// eslint-disable-next-line camelcase
// eslint-disable-next-line
UNSAFE_componentWillUpdate(props, state) {
if (this.shouldTrackPageChange(this.state, state)) {
this.startPageChangeTimer();
@ -561,7 +563,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
switch (payload.action) {
case 'MatrixActions.accountData':
// XXX: This is a collection of several hacks to solve a minor problem. We want to
// update our local state when the ID server changes, but don't want to put that in
// update our local state when the identity server changes, but don't want to put that in
// the js-sdk as we'd be then dictating how all consumers need to behave. However,
// this component is already bloated and we probably don't want this tiny logic in
// here, but there's no better place in the react-sdk for it. Additionally, we're
@ -627,6 +629,9 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
case 'forget_room':
this.forgetRoom(payload.room_id);
break;
case 'copy_room':
this.copyRoom(payload.room_id);
break;
case 'reject_invite':
Modal.createTrackedDialog('Reject invitation', '', QuestionDialog, {
title: _t('Reject invitation'),
@ -1099,7 +1104,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
private leaveRoomWarnings(roomId: string) {
const roomToLeave = MatrixClientPeg.get().getRoom(roomId);
const isSpace = SettingsStore.getValue("feature_spaces") && roomToLeave?.isSpaceRoom();
const isSpace = SpaceStore.spacesEnabled && roomToLeave?.isSpaceRoom();
// Show a warning if there are additional complications.
const warnings = [];
@ -1107,7 +1112,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
if (memberCount === 1) {
warnings.push((
<span className="warning" key="only_member_warning">
{' '/* Whitespace, otherwise the sentences get smashed together */ }
{ ' '/* Whitespace, otherwise the sentences get smashed together */ }
{ _t("You are the only person here. " +
"If you leave, no one will be able to join in the future, including you.") }
</span>
@ -1122,7 +1127,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
if (rule !== "public") {
warnings.push((
<span className="warning" key="non_public_warning">
{' '/* Whitespace, otherwise the sentences get smashed together */ }
{ ' '/* Whitespace, otherwise the sentences get smashed together */ }
{ isSpace
? _t("This space is not public. You will not be able to rejoin without an invite.")
: _t("This room is not public. You will not be able to rejoin without an invite.") }
@ -1137,7 +1142,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
const roomToLeave = MatrixClientPeg.get().getRoom(roomId);
const warnings = this.leaveRoomWarnings(roomId);
const isSpace = SettingsStore.getValue("feature_spaces") && roomToLeave?.isSpaceRoom();
const isSpace = SpaceStore.spacesEnabled && roomToLeave?.isSpaceRoom();
Modal.createTrackedDialog(isSpace ? "Leave space" : "Leave room", '', QuestionDialog, {
title: isSpace ? _t("Leave space") : _t("Leave room"),
description: (
@ -1150,7 +1155,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
: _t(
"Are you sure you want to leave the room '%(roomName)s'?",
{ roomName: roomToLeave.name },
)}
) }
{ warnings }
</span>
),
@ -1193,6 +1198,17 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
});
}
private async copyRoom(roomId: string) {
const roomLink = makeRoomPermalink(roomId);
const success = await copyPlaintext(roomLink);
if (!success) {
Modal.createTrackedDialog("Unable to copy room link", "", ErrorDialog, {
title: _t("Unable to copy room link"),
description: _t("Unable to copy a link to the room to the clipboard."),
});
}
}
/**
* Starts a chat with the welcome user, if the user doesn't already have one
* @returns {string} The room ID of the new room, or null if no room was created
@ -1687,7 +1703,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
const type = screen === "start_sso" ? "sso" : "cas";
PlatformPeg.get().startSingleSignOn(cli, type, this.getFragmentAfterLogin());
} else if (screen === 'groups') {
if (SettingsStore.getValue("feature_spaces")) {
if (SpaceStore.spacesEnabled) {
dis.dispatch({ action: "view_home_page" });
return;
}
@ -1774,7 +1790,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
subAction: params.action,
});
} else if (screen.indexOf('group/') === 0) {
if (SettingsStore.getValue("feature_spaces")) {
if (SpaceStore.spacesEnabled) {
dis.dispatch({ action: "view_home_page" });
return;
}
@ -1848,13 +1864,6 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
dis.dispatch({ action: 'timeline_resize' });
}
onRoomCreated(roomId: string) {
dis.dispatch({
action: "view_room",
room_id: roomId,
});
}
onRegisterClick = () => {
this.showScreen("register");
};
@ -1936,7 +1945,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
this.setState({ serverConfig });
};
private makeRegistrationUrl = (params: {[key: string]: string}) => {
private makeRegistrationUrl = (params: QueryDict) => {
if (this.props.startingFragmentQueryParams.referrer) {
params.referrer = this.props.startingFragmentQueryParams.referrer;
}
@ -2027,7 +2036,6 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
{...this.state}
ref={this.loggedInView}
matrixClient={MatrixClientPeg.get()}
onRoomCreated={this.onRoomCreated}
onRegistered={this.onRegistered}
currentRoomId={this.state.currentRoomId}
/>
@ -2037,15 +2045,15 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
let errorBox;
if (this.state.syncError && !isStoreError) {
errorBox = <div className="mx_MatrixChat_syncError">
{messageForSyncError(this.state.syncError)}
{ messageForSyncError(this.state.syncError) }
</div>;
}
view = (
<div className="mx_MatrixChat_splash">
{errorBox}
{ errorBox }
<Spinner />
<a href="#" className="mx_MatrixChat_splashButtons" onClick={this.onLogoutClick}>
{_t('Logout')}
{ _t('Logout') }
</a>
</div>
);
@ -2091,7 +2099,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
onForgotPasswordClick={showPasswordReset ? this.onForgotPasswordClick : undefined}
onServerConfigChange={this.onServerConfigChange}
fragmentAfterLogin={fragmentAfterLogin}
defaultUsername={this.props.startingFragmentQueryParams.defaultUsername}
defaultUsername={this.props.startingFragmentQueryParams.defaultUsername as string}
{...this.getServerProperties()}
/>
);
@ -2108,7 +2116,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
}
return <ErrorBoundary>
{view}
{ view }
</ErrorBoundary>;
}
}

View file

@ -36,6 +36,7 @@ import DMRoomMap from "../../utils/DMRoomMap";
import NewRoomIntro from "../views/rooms/NewRoomIntro";
import { replaceableComponent } from "../../utils/replaceableComponent";
import defaultDispatcher from '../../dispatcher/dispatcher';
import CallEventGrouper from "./CallEventGrouper";
import WhoIsTypingTile from '../views/rooms/WhoIsTypingTile';
import ScrollPanel, { IScrollState } from "./ScrollPanel";
import EventListSummary from '../views/elements/EventListSummary';
@ -59,7 +60,11 @@ const groupedEvents = [
// check if there is a previous event and it has the same sender as this event
// and the types are the same/is in continuedTypes and the time between them is <= CONTINUATION_MAX_INTERVAL
function shouldFormContinuation(prevEvent: MatrixEvent, mxEvent: MatrixEvent): boolean {
function shouldFormContinuation(
prevEvent: MatrixEvent,
mxEvent: MatrixEvent,
showHiddenEvents: boolean,
): boolean {
// sanity check inputs
if (!prevEvent || !prevEvent.sender || !mxEvent.sender) return false;
// check if within the max continuation period
@ -79,7 +84,7 @@ function shouldFormContinuation(prevEvent: MatrixEvent, mxEvent: MatrixEvent): b
mxEvent.sender.getMxcAvatarUrl() !== prevEvent.sender.getMxcAvatarUrl()) return false;
// if we don't have tile for previous event then it was shown by showHiddenEvents and has no SenderProfile
if (!haveTileForEvent(prevEvent)) return false;
if (!haveTileForEvent(prevEvent, showHiddenEvents)) return false;
return true;
}
@ -233,6 +238,11 @@ export default class MessagePanel extends React.Component<IProps, IState> {
private readonly showTypingNotificationsWatcherRef: string;
private eventNodes: Record<string, HTMLElement>;
// A map of <callId, CallEventGrouper>
private callEventGroupers = new Map<string, CallEventGrouper>();
private membersCount = 0;
constructor(props, context) {
super(props, context);
@ -244,7 +254,8 @@ export default class MessagePanel extends React.Component<IProps, IState> {
};
// Cache hidden events setting on mount since Settings is expensive to
// query, and we check this in a hot code path.
// query, and we check this in a hot code path. This is also cached in
// our RoomContext, however we still need a fallback for roomless MessagePanels.
this.showHiddenEventsInTimeline = SettingsStore.getValue("showHiddenEventsInTimeline");
this.showTypingNotificationsWatcherRef =
@ -252,11 +263,14 @@ export default class MessagePanel extends React.Component<IProps, IState> {
}
componentDidMount() {
this.calculateRoomMembersCount();
this.props.room?.on("RoomState.members", this.calculateRoomMembersCount);
this.isMounted = true;
}
componentWillUnmount() {
this.isMounted = false;
this.props.room?.off("RoomState.members", this.calculateRoomMembersCount);
SettingsStore.unwatchSetting(this.showTypingNotificationsWatcherRef);
}
@ -270,6 +284,10 @@ export default class MessagePanel extends React.Component<IProps, IState> {
}
}
private calculateRoomMembersCount = (): void => {
this.membersCount = this.props.room?.getMembers().length || 0;
};
private onShowTypingNotificationsChange = (): void => {
this.setState({
showTypingNotifications: SettingsStore.getValue("showTypingNotifications"),
@ -404,17 +422,21 @@ export default class MessagePanel extends React.Component<IProps, IState> {
return !this.isMounted;
};
private get showHiddenEvents(): boolean {
return this.context?.showHiddenEventsInTimeline ?? this.showHiddenEventsInTimeline;
}
// TODO: Implement granular (per-room) hide options
public shouldShowEvent(mxEv: MatrixEvent): boolean {
if (mxEv.sender && MatrixClientPeg.get().isUserIgnored(mxEv.sender.userId)) {
if (MatrixClientPeg.get().isUserIgnored(mxEv.getSender())) {
return false; // ignored = no show (only happens if the ignore happens after an event was received)
}
if (this.showHiddenEventsInTimeline) {
if (this.showHiddenEvents) {
return true;
}
if (!haveTileForEvent(mxEv)) {
if (!haveTileForEvent(mxEv, this.showHiddenEvents)) {
return false; // no tile = no show
}
@ -572,9 +594,23 @@ export default class MessagePanel extends React.Component<IProps, IState> {
const last = (mxEv === lastShownEvent);
const { nextEvent, nextTile } = this.getNextEventInfo(this.props.events, i);
if (
mxEv.getType().indexOf("m.call.") === 0 ||
mxEv.getType().indexOf("org.matrix.call.") === 0
) {
const callId = mxEv.getContent().call_id;
if (this.callEventGroupers.has(callId)) {
this.callEventGroupers.get(callId).add(mxEv);
} else {
const callEventGrouper = new CallEventGrouper();
callEventGrouper.add(mxEv);
this.callEventGroupers.set(callId, callEventGrouper);
}
}
if (grouper) {
if (grouper.shouldGroup(mxEv)) {
grouper.add(mxEv);
grouper.add(mxEv, this.showHiddenEvents);
continue;
} else {
// not part of group, so get the group tiles, close the
@ -649,12 +685,15 @@ export default class MessagePanel extends React.Component<IProps, IState> {
}
let willWantDateSeparator = false;
let lastInSection = true;
if (nextEvent) {
willWantDateSeparator = this.wantsDateSeparator(mxEv, nextEvent.getDate() || new Date());
lastInSection = willWantDateSeparator || mxEv.getSender() !== nextEvent.getSender();
}
// is this a continuation of the previous message?
const continuation = !wantsDateSeparator && shouldFormContinuation(prevEvent, mxEv);
const continuation = !wantsDateSeparator &&
shouldFormContinuation(prevEvent, mxEv, this.showHiddenEvents);
const eventId = mxEv.getId();
const highlight = (eventId === this.props.highlightedEventId);
@ -685,6 +724,7 @@ export default class MessagePanel extends React.Component<IProps, IState> {
// it's successful: we received it.
isLastSuccessful = isLastSuccessful && mxEv.getSender() === MatrixClientPeg.get().getUserId();
const callEventGrouper = this.callEventGroupers.get(mxEv.getContent().call_id);
// use txnId as key if available so that we don't remount during sending
ret.push(
<TileErrorBoundary key={mxEv.getTxnId() || eventId} mxEvent={mxEv}>
@ -707,7 +747,7 @@ export default class MessagePanel extends React.Component<IProps, IState> {
isTwelveHour={this.props.isTwelveHour}
permalinkCreator={this.props.permalinkCreator}
last={last}
lastInSection={willWantDateSeparator}
lastInSection={lastInSection}
lastSuccessful={isLastSuccessful}
isSelectedEvent={highlight}
getRelationsForEvent={this.props.getRelationsForEvent}
@ -715,6 +755,8 @@ export default class MessagePanel extends React.Component<IProps, IState> {
layout={this.props.layout}
enableFlair={this.props.enableFlair}
showReadReceipts={this.props.showReadReceipts}
callEventGrouper={callEventGrouper}
hideSender={this.membersCount <= 2 && this.props.layout === Layout.Bubble}
/>
</TileErrorBoundary>,
);
@ -951,7 +993,7 @@ abstract class BaseGrouper {
}
public abstract shouldGroup(ev: MatrixEvent): boolean;
public abstract add(ev: MatrixEvent): void;
public abstract add(ev: MatrixEvent, showHiddenEvents?: boolean): void;
public abstract getTiles(): ReactNode[];
public abstract getNewPrevEvent(): MatrixEvent;
}
@ -1205,10 +1247,10 @@ class MemberGrouper extends BaseGrouper {
return groupedEvents.includes(ev.getType() as EventType);
}
public add(ev: MatrixEvent): void {
public add(ev: MatrixEvent, showHiddenEvents?: boolean): void {
if (ev.getType() === EventType.RoomMember) {
// We can ignore any events that don't actually have a message to display
if (!hasText(ev)) return;
if (!hasText(ev, showHiddenEvents)) return;
}
this.readMarker = this.readMarker || this.panel.readMarkerForEvent(
ev.getId(),

View file

@ -121,7 +121,7 @@ export default class MyGroups extends React.Component {
) }
</div>
</div>
{/*<div className="mx_MyGroups_joinBox mx_MyGroups_headerCard">
{ /*<div className="mx_MyGroups_joinBox mx_MyGroups_headerCard">
<AccessibleButton className='mx_MyGroups_headerCard_button' onClick={this._onJoinGroupClick}>
<img src={require("../../../res/img/icons-create-room.svg")} width="50" height="50" />
</AccessibleButton>
@ -137,7 +137,7 @@ export default class MyGroups extends React.Component {
{ 'i': (sub) => <i>{ sub }</i> })
}
</div>
</div>*/}
</div>*/ }
</div>
<BetaCard featureId="feature_spaces" title={_t("Communities are changing to Spaces")} />
<div className="mx_MyGroups_content">

View file

@ -51,14 +51,14 @@ export default class NonUrgentToastContainer extends React.PureComponent<IProps,
const toasts = this.state.toasts.map((t, i) => {
return (
<div className="mx_NonUrgentToastContainer_toast" key={`toast-${i}`}>
{React.createElement(t, {})}
{ React.createElement(t, {}) }
</div>
);
});
return (
<div className="mx_NonUrgentToastContainer" role="alert">
{toasts}
{ toasts }
</div>
);
}

View file

@ -35,8 +35,8 @@ interface IProps {
export default class NotificationPanel extends React.PureComponent<IProps> {
render() {
const emptyState = (<div className="mx_RightPanel_empty mx_NotificationPanel_empty">
<h2>{_t('Youre all caught up')}</h2>
<p>{_t('You have no visible notifications.')}</p>
<h2>{ _t('Youre all caught up') }</h2>
<p>{ _t('You have no visible notifications.') }</p>
</div>);
let content;

View file

@ -17,6 +17,7 @@ limitations under the License.
import React from 'react';
import { Room } from "matrix-js-sdk/src/models/room";
import { RoomState } from "matrix-js-sdk/src/models/room-state";
import { User } from "matrix-js-sdk/src/models/user";
import { RoomMember } from "matrix-js-sdk/src/models/room-member";
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
@ -48,6 +49,7 @@ import NotificationPanel from "./NotificationPanel";
import ResizeNotifier from "../../utils/ResizeNotifier";
import PinnedMessagesCard from "../views/right_panel/PinnedMessagesCard";
import { throttle } from 'lodash';
import SpaceStore from "../../stores/SpaceStore";
interface IProps {
room?: Room; // if showing panels for a given room, this is set
@ -107,7 +109,7 @@ export default class RightPanel extends React.Component<IProps, IState> {
return RightPanelPhases.GroupMemberList;
}
return rps.groupPanelPhase;
} else if (SettingsStore.getValue("feature_spaces") && this.props.room?.isSpaceRoom()
} else if (SpaceStore.spacesEnabled && this.props.room?.isSpaceRoom()
&& !RIGHT_PANEL_SPACE_PHASES.includes(rps.roomPanelPhase)
) {
return RightPanelPhases.SpaceMemberList;
@ -151,7 +153,7 @@ export default class RightPanel extends React.Component<IProps, IState> {
}
// TODO: [REACT-WARNING] Replace with appropriate lifecycle event
UNSAFE_componentWillReceiveProps(newProps) { // eslint-disable-line camelcase
UNSAFE_componentWillReceiveProps(newProps) { // eslint-disable-line
if (newProps.groupId !== this.props.groupId) {
this.unregisterGroupStore();
this.initGroupStore(newProps.groupId);
@ -173,7 +175,7 @@ export default class RightPanel extends React.Component<IProps, IState> {
});
};
private onRoomStateMember = (ev: MatrixEvent, _, member: RoomMember) => {
private onRoomStateMember = (ev: MatrixEvent, state: RoomState, member: RoomMember) => {
if (!this.props.room || member.roomId !== this.props.room.roomId) {
return;
}

View file

@ -16,6 +16,9 @@ limitations under the License.
*/
import React from "react";
import { IFieldType, IInstance, IProtocol, IPublicRoomsChunkRoom } from "matrix-js-sdk/src/client";
import { Visibility } from "matrix-js-sdk/src/@types/partials";
import { IRoomDirectoryOptions } from "matrix-js-sdk/src/@types/requests";
import { MatrixClientPeg } from "../../MatrixClientPeg";
import dis from "../../dispatcher/dispatcher";
@ -25,7 +28,7 @@ import { _t } from '../../languageHandler';
import SdkConfig from '../../SdkConfig';
import { instanceForInstanceId, protocolNameForInstanceId } from '../../utils/DirectoryUtils';
import Analytics from '../../Analytics';
import { ALL_ROOMS, IFieldType, IInstance, IProtocol, Protocols } from "../views/directory/NetworkDropdown";
import NetworkDropdown, { ALL_ROOMS, Protocols } from "../views/directory/NetworkDropdown";
import SettingsStore from "../../settings/SettingsStore";
import GroupFilterOrderStore from "../../stores/GroupFilterOrderStore";
import GroupStore from "../../stores/GroupStore";
@ -40,10 +43,10 @@ import ErrorDialog from "../views/dialogs/ErrorDialog";
import QuestionDialog from "../views/dialogs/QuestionDialog";
import BaseDialog from "../views/dialogs/BaseDialog";
import DirectorySearchBox from "../views/elements/DirectorySearchBox";
import NetworkDropdown from "../views/directory/NetworkDropdown";
import ScrollPanel from "./ScrollPanel";
import Spinner from "../views/elements/Spinner";
import { ActionPayload } from "../../dispatcher/payloads";
import { getDisplayAliasForAliasSet } from "../../Rooms";
const MAX_NAME_LENGTH = 80;
const MAX_TOPIC_LENGTH = 800;
@ -60,7 +63,7 @@ interface IProps extends IDialogProps {
}
interface IState {
publicRooms: IRoom[];
publicRooms: IPublicRoomsChunkRoom[];
loading: boolean;
protocolsLoading: boolean;
error?: string;
@ -71,35 +74,12 @@ interface IState {
communityName?: string;
}
/* eslint-disable camelcase */
interface IRoom {
room_id: string;
name?: string;
avatar_url?: string;
topic?: string;
canonical_alias?: string;
aliases?: string[];
world_readable: boolean;
guest_can_join: boolean;
num_joined_members: number;
}
interface IPublicRoomsRequest {
limit?: number;
since?: string;
server?: string;
filter?: object;
include_all_networks?: boolean;
third_party_instance_id?: string;
}
/* eslint-enable camelcase */
@replaceableComponent("structures.RoomDirectory")
export default class RoomDirectory extends React.Component<IProps, IState> {
private readonly startTime: number;
private unmounted = false;
private nextBatch: string = null;
private filterTimeout: NodeJS.Timeout;
private filterTimeout: number;
private protocols: Protocols;
constructor(props) {
@ -252,7 +232,7 @@ export default class RoomDirectory extends React.Component<IProps, IState> {
// remember the next batch token when we sent the request
// too. If it's changed, appending to the list will corrupt it.
const nextBatch = this.nextBatch;
const opts: IPublicRoomsRequest = { limit: 20 };
const opts: IRoomDirectoryOptions = { limit: 20 };
if (roomServer != MatrixClientPeg.getHomeserverName()) {
opts.server = roomServer;
}
@ -325,7 +305,7 @@ export default class RoomDirectory extends React.Component<IProps, IState> {
* HS admins to do this through the RoomSettings interface, but
* this needs SPEC-417.
*/
private removeFromDirectory(room: IRoom) {
private removeFromDirectory(room: IPublicRoomsChunkRoom) {
const alias = getDisplayAliasForRoom(room);
const name = room.name || alias || _t('Unnamed room');
@ -345,7 +325,7 @@ export default class RoomDirectory extends React.Component<IProps, IState> {
const modal = Modal.createDialog(Spinner);
let step = _t('remove %(name)s from the directory.', { name: name });
MatrixClientPeg.get().setRoomDirectoryVisibility(room.room_id, 'private').then(() => {
MatrixClientPeg.get().setRoomDirectoryVisibility(room.room_id, Visibility.Private).then(() => {
if (!alias) return;
step = _t('delete the address.');
return MatrixClientPeg.get().deleteAlias(alias);
@ -367,7 +347,7 @@ export default class RoomDirectory extends React.Component<IProps, IState> {
});
}
private onRoomClicked = (room: IRoom, ev: ButtonEvent) => {
private onRoomClicked = (room: IPublicRoomsChunkRoom, ev: ButtonEvent) => {
// If room was shift-clicked, remove it from the room directory
if (ev.shiftKey && !this.state.selectedCommunityId) {
ev.preventDefault();
@ -480,17 +460,17 @@ export default class RoomDirectory extends React.Component<IProps, IState> {
}
};
private onPreviewClick = (ev: ButtonEvent, room: IRoom) => {
private onPreviewClick = (ev: ButtonEvent, room: IPublicRoomsChunkRoom) => {
this.showRoom(room, null, false, true);
ev.stopPropagation();
};
private onViewClick = (ev: ButtonEvent, room: IRoom) => {
private onViewClick = (ev: ButtonEvent, room: IPublicRoomsChunkRoom) => {
this.showRoom(room);
ev.stopPropagation();
};
private onJoinClick = (ev: ButtonEvent, room: IRoom) => {
private onJoinClick = (ev: ButtonEvent, room: IPublicRoomsChunkRoom) => {
this.showRoom(room, null, true);
ev.stopPropagation();
};
@ -508,7 +488,7 @@ export default class RoomDirectory extends React.Component<IProps, IState> {
this.showRoom(null, alias, autoJoin);
}
private showRoom(room: IRoom, roomAlias?: string, autoJoin = false, shouldPeek = false) {
private showRoom(room: IPublicRoomsChunkRoom, roomAlias?: string, autoJoin = false, shouldPeek = false) {
this.onFinished();
const payload: ActionPayload = {
action: 'view_room',
@ -557,7 +537,7 @@ export default class RoomDirectory extends React.Component<IProps, IState> {
dis.dispatch(payload);
}
private createRoomCells(room: IRoom) {
private createRoomCells(room: IPublicRoomsChunkRoom) {
const client = MatrixClientPeg.get();
const clientRoom = client.getRoom(room.room_id);
const hasJoinedRoom = clientRoom && clientRoom.getMyMembership() === "join";
@ -609,7 +589,7 @@ export default class RoomDirectory extends React.Component<IProps, IState> {
// We use onMouseDown instead of onClick, so that we can avoid text getting selected
return [
<div
key={ `${room.room_id}_avatar` }
key={`${room.room_id}_avatar`}
onMouseDown={(ev) => this.onRoomClicked(room, ev)}
className="mx_RoomDirectory_roomAvatar"
>
@ -623,7 +603,7 @@ export default class RoomDirectory extends React.Component<IProps, IState> {
/>
</div>,
<div
key={ `${room.room_id}_description` }
key={`${room.room_id}_description`}
onMouseDown={(ev) => this.onRoomClicked(room, ev)}
className="mx_RoomDirectory_roomDescription"
>
@ -646,14 +626,14 @@ export default class RoomDirectory extends React.Component<IProps, IState> {
</div>
</div>,
<div
key={ `${room.room_id}_memberCount` }
key={`${room.room_id}_memberCount`}
onMouseDown={(ev) => this.onRoomClicked(room, ev)}
className="mx_RoomDirectory_roomMemberCount"
>
{ room.num_joined_members }
</div>,
<div
key={ `${room.room_id}_preview` }
key={`${room.room_id}_preview`}
onMouseDown={(ev) => this.onRoomClicked(room, ev)}
// cancel onMouseDown otherwise shift-clicking highlights text
className="mx_RoomDirectory_preview"
@ -661,7 +641,7 @@ export default class RoomDirectory extends React.Component<IProps, IState> {
{ previewButton }
</div>,
<div
key={ `${room.room_id}_join` }
key={`${room.room_id}_join`}
onMouseDown={(ev) => this.onRoomClicked(room, ev)}
className="mx_RoomDirectory_join"
>
@ -816,7 +796,7 @@ export default class RoomDirectory extends React.Component<IProps, IState> {
showJoinButton={showJoinButton}
initialText={this.props.initialText}
/>
{dropdown}
{ dropdown }
</div>;
}
const explanation =
@ -834,16 +814,16 @@ export default class RoomDirectory extends React.Component<IProps, IState> {
}) : _t("Explore rooms");
return (
<BaseDialog
className={'mx_RoomDirectory_dialog'}
className="mx_RoomDirectory_dialog"
hasCancel={true}
onFinished={this.onFinished}
title={title}
>
<div className="mx_RoomDirectory">
{explanation}
{ explanation }
<div className="mx_RoomDirectory_list">
{listHeader}
{content}
{ listHeader }
{ content }
</div>
</div>
</BaseDialog>
@ -853,6 +833,6 @@ export default class RoomDirectory extends React.Component<IProps, IState> {
// Similar to matrix-react-sdk's MatrixTools.getDisplayAliasForRoom
// but works with the objects we get from the public room list
function getDisplayAliasForRoom(room: IRoom) {
return room.canonical_alias || room.aliases?.[0] || "";
function getDisplayAliasForRoom(room: IPublicRoomsChunkRoom) {
return getDisplayAliasForAliasSet(room.canonical_alias, room.aliases);
}

View file

@ -209,9 +209,9 @@ export default class RoomSearch extends React.PureComponent<IProps, IState> {
return (
<div className={classes}>
{icon}
{input}
{clearButton}
{ icon }
{ input }
{ clearButton }
</div>
);
}

View file

@ -222,17 +222,17 @@ export default class RoomStatusBar extends React.PureComponent {
let buttonRow = <>
<AccessibleButton onClick={this._onCancelAllClick} className="mx_RoomStatusBar_unsentCancelAllBtn">
{_t("Delete all")}
{ _t("Delete all") }
</AccessibleButton>
<AccessibleButton onClick={this._onResendAllClick} className="mx_RoomStatusBar_unsentResendAllBtn">
{_t("Retry all")}
{ _t("Retry all") }
</AccessibleButton>
</>;
if (this.state.isResending) {
buttonRow = <>
<InlineSpinner w={20} h={20} />
{/* span for css */}
<span>{_t("Sending")}</span>
{ /* span for css */ }
<span>{ _t("Sending") }</span>
</>;
}
@ -253,7 +253,7 @@ export default class RoomStatusBar extends React.PureComponent {
</div>
</div>
<div className="mx_RoomStatusBar_unsentButtonBar">
{buttonRow}
{ buttonRow }
</div>
</div>
</div>
@ -270,10 +270,10 @@ export default class RoomStatusBar extends React.PureComponent {
height="24" title="/!\ " alt="/!\ " />
<div>
<div className="mx_RoomStatusBar_connectionLostBar_title">
{_t('Connectivity to the server has been lost.')}
{ _t('Connectivity to the server has been lost.') }
</div>
<div className="mx_RoomStatusBar_connectionLostBar_desc">
{_t('Sent messages will be stored until your connection has returned.')}
{ _t('Sent messages will be stored until your connection has returned.') }
</div>
</div>
</div>

View file

@ -25,8 +25,8 @@ import React, { createRef } from 'react';
import classNames from 'classnames';
import { IRecommendedVersion, NotificationCountType, Room } from "matrix-js-sdk/src/models/room";
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
import { SearchResult } from "matrix-js-sdk/src/models/search-result";
import { EventSubscription } from "fbemitter";
import { ISearchResults } from 'matrix-js-sdk/src/@types/search';
import shouldHideEvent from '../../shouldHideEvent';
import { _t } from '../../languageHandler';
@ -89,6 +89,7 @@ import RoomStatusBar from "./RoomStatusBar";
import MessageComposer from '../views/rooms/MessageComposer';
import JumpToBottomButton from "../views/rooms/JumpToBottomButton";
import TopUnreadMessagesBar from "../views/rooms/TopUnreadMessagesBar";
import SpaceStore from "../../stores/SpaceStore";
const DEBUG = false;
let debuglog = function(msg: string) {};
@ -133,12 +134,7 @@ export interface IState {
searching: boolean;
searchTerm?: string;
searchScope?: SearchScope;
searchResults?: XOR<{}, {
count: number;
highlights: string[];
results: SearchResult[];
next_batch: string; // eslint-disable-line camelcase
}>;
searchResults?: XOR<{}, ISearchResults>;
searchHighlights?: string[];
searchInProgress?: boolean;
callState?: CallState;
@ -170,6 +166,11 @@ export interface IState {
canReply: boolean;
layout: Layout;
lowBandwidth: boolean;
alwaysShowTimestamps: boolean;
showTwelveHourTimestamps: boolean;
readMarkerInViewThresholdMs: number;
readMarkerOutOfViewThresholdMs: number;
showHiddenEventsInTimeline: boolean;
showReadReceipts: boolean;
showRedactions: boolean;
showJoinLeaves: boolean;
@ -234,6 +235,11 @@ export default class RoomView extends React.Component<IProps, IState> {
canReply: false,
layout: SettingsStore.getValue("layout"),
lowBandwidth: SettingsStore.getValue("lowBandwidth"),
alwaysShowTimestamps: SettingsStore.getValue("alwaysShowTimestamps"),
showTwelveHourTimestamps: SettingsStore.getValue("showTwelveHourTimestamps"),
readMarkerInViewThresholdMs: SettingsStore.getValue("readMarkerInViewThresholdMs"),
readMarkerOutOfViewThresholdMs: SettingsStore.getValue("readMarkerOutOfViewThresholdMs"),
showHiddenEventsInTimeline: SettingsStore.getValue("showHiddenEventsInTimeline"),
showReadReceipts: true,
showRedactions: true,
showJoinLeaves: true,
@ -257,7 +263,6 @@ export default class RoomView extends React.Component<IProps, IState> {
this.context.on("userTrustStatusChanged", this.onUserVerificationChanged);
this.context.on("crossSigning.keysChanged", this.onCrossSigningKeysChanged);
this.context.on("Event.decrypted", this.onEventDecrypted);
this.context.on("event", this.onEvent);
// Start listening for RoomViewStore updates
this.roomStoreToken = RoomViewStore.addListener(this.onRoomViewStoreUpdate);
this.rightPanelStoreToken = RightPanelStore.getSharedInstance().addListener(this.onRightPanelStoreUpdate);
@ -266,11 +271,26 @@ export default class RoomView extends React.Component<IProps, IState> {
WidgetStore.instance.on(UPDATE_EVENT, this.onWidgetStoreUpdate);
this.settingWatchers = [
SettingsStore.watchSetting("layout", null, () =>
this.setState({ layout: SettingsStore.getValue("layout") }),
SettingsStore.watchSetting("layout", null, (...[,,, value]) =>
this.setState({ layout: value as Layout }),
),
SettingsStore.watchSetting("lowBandwidth", null, () =>
this.setState({ lowBandwidth: SettingsStore.getValue("lowBandwidth") }),
SettingsStore.watchSetting("lowBandwidth", null, (...[,,, value]) =>
this.setState({ lowBandwidth: value as boolean }),
),
SettingsStore.watchSetting("alwaysShowTimestamps", null, (...[,,, value]) =>
this.setState({ alwaysShowTimestamps: value as boolean }),
),
SettingsStore.watchSetting("showTwelveHourTimestamps", null, (...[,,, value]) =>
this.setState({ showTwelveHourTimestamps: value as boolean }),
),
SettingsStore.watchSetting("readMarkerInViewThresholdMs", null, (...[,,, value]) =>
this.setState({ readMarkerInViewThresholdMs: value as number }),
),
SettingsStore.watchSetting("readMarkerOutOfViewThresholdMs", null, (...[,,, value]) =>
this.setState({ readMarkerOutOfViewThresholdMs: value as number }),
),
SettingsStore.watchSetting("showHiddenEventsInTimeline", null, (...[,,, value]) =>
this.setState({ showHiddenEventsInTimeline: value as boolean }),
),
];
}
@ -337,30 +357,20 @@ export default class RoomView extends React.Component<IProps, IState> {
// Add watchers for each of the settings we just looked up
this.settingWatchers = this.settingWatchers.concat([
SettingsStore.watchSetting("showReadReceipts", null, () =>
this.setState({
showReadReceipts: SettingsStore.getValue("showReadReceipts", roomId),
}),
SettingsStore.watchSetting("showReadReceipts", roomId, (...[,,, value]) =>
this.setState({ showReadReceipts: value as boolean }),
),
SettingsStore.watchSetting("showRedactions", null, () =>
this.setState({
showRedactions: SettingsStore.getValue("showRedactions", roomId),
}),
SettingsStore.watchSetting("showRedactions", roomId, (...[,,, value]) =>
this.setState({ showRedactions: value as boolean }),
),
SettingsStore.watchSetting("showJoinLeaves", null, () =>
this.setState({
showJoinLeaves: SettingsStore.getValue("showJoinLeaves", roomId),
}),
SettingsStore.watchSetting("showJoinLeaves", roomId, (...[,,, value]) =>
this.setState({ showJoinLeaves: value as boolean }),
),
SettingsStore.watchSetting("showAvatarChanges", null, () =>
this.setState({
showAvatarChanges: SettingsStore.getValue("showAvatarChanges", roomId),
}),
SettingsStore.watchSetting("showAvatarChanges", roomId, (...[,,, value]) =>
this.setState({ showAvatarChanges: value as boolean }),
),
SettingsStore.watchSetting("showDisplaynameChanges", null, () =>
this.setState({
showDisplaynameChanges: SettingsStore.getValue("showDisplaynameChanges", roomId),
}),
SettingsStore.watchSetting("showDisplaynameChanges", roomId, (...[,,, value]) =>
this.setState({ showDisplaynameChanges: value as boolean }),
),
]);
@ -641,7 +651,6 @@ export default class RoomView extends React.Component<IProps, IState> {
this.context.removeListener("userTrustStatusChanged", this.onUserVerificationChanged);
this.context.removeListener("crossSigning.keysChanged", this.onCrossSigningKeysChanged);
this.context.removeListener("Event.decrypted", this.onEventDecrypted);
this.context.removeListener("event", this.onEvent);
}
window.removeEventListener('beforeunload', this.onPageUnload);
@ -841,8 +850,7 @@ export default class RoomView extends React.Component<IProps, IState> {
if (this.unmounted) return;
// ignore events for other rooms
if (!room) return;
if (!this.state.room || room.roomId != this.state.room.roomId) return;
if (!room || room.roomId !== this.state.room?.roomId) return;
// ignore events from filtered timelines
if (data.timeline.getTimelineSet() !== room.getUnfilteredTimelineSet()) return;
@ -863,6 +871,10 @@ export default class RoomView extends React.Component<IProps, IState> {
// we'll only be showing a spinner.
if (this.state.joining) return;
if (!ev.isBeingDecrypted() && !ev.isDecryptionFailure()) {
this.handleEffects(ev);
}
if (ev.getSender() !== this.context.credentials.userId) {
// update unread count when scrolled up
if (!this.state.searchResults && this.state.atEndOfLiveTimeline) {
@ -875,20 +887,14 @@ export default class RoomView extends React.Component<IProps, IState> {
}
};
private onEventDecrypted = (ev) => {
private onEventDecrypted = (ev: MatrixEvent) => {
if (!this.state.room || !this.state.matrixClientIsReady) return; // not ready at all
if (ev.getRoomId() !== this.state.room.roomId) return; // not for us
if (ev.isDecryptionFailure()) return;
this.handleEffects(ev);
};
private onEvent = (ev) => {
if (ev.isBeingDecrypted() || ev.isDecryptionFailure()) return;
this.handleEffects(ev);
};
private handleEffects = (ev) => {
if (!this.state.room || !this.state.matrixClientIsReady) return; // not ready at all
if (ev.getRoomId() !== this.state.room.roomId) return; // not for us
private handleEffects = (ev: MatrixEvent) => {
const notifState = RoomNotificationStateStore.instance.getRoomState(this.state.room);
if (!notifState.isUnread) return;
@ -921,6 +927,7 @@ export default class RoomView extends React.Component<IProps, IState> {
// called when state.room is first initialised (either at initial load,
// after a successful peek, or after we join the room).
private onRoomLoaded = (room: Room) => {
if (this.unmounted) return;
// Attach a widget store listener only when we get a room
WidgetLayoutStore.instance.on(WidgetLayoutStore.emissionForRoom(room), this.onWidgetLayoutChange);
this.onWidgetLayoutChange(); // provoke an update
@ -935,9 +942,9 @@ export default class RoomView extends React.Component<IProps, IState> {
};
private async calculateRecommendedVersion(room: Room) {
this.setState({
upgradeRecommendation: await room.getRecommendedVersion(),
});
const upgradeRecommendation = await room.getRecommendedVersion();
if (this.unmounted) return;
this.setState({ upgradeRecommendation });
}
private async loadMembersIfJoined(room: Room) {
@ -1027,23 +1034,19 @@ export default class RoomView extends React.Component<IProps, IState> {
};
private async updateE2EStatus(room: Room) {
if (!this.context.isRoomEncrypted(room.roomId)) {
return;
}
if (!this.context.isCryptoEnabled()) {
// If crypto is not currently enabled, we aren't tracking devices at all,
// so we don't know what the answer is. Let's error on the safe side and show
// a warning for this case.
this.setState({
e2eStatus: E2EStatus.Warning,
});
return;
if (!this.context.isRoomEncrypted(room.roomId)) return;
// If crypto is not currently enabled, we aren't tracking devices at all,
// so we don't know what the answer is. Let's error on the safe side and show
// a warning for this case.
let e2eStatus = E2EStatus.Warning;
if (this.context.isCryptoEnabled()) {
/* At this point, the user has encryption on and cross-signing on */
e2eStatus = await shieldStatusForRoom(this.context, room);
}
/* At this point, the user has encryption on and cross-signing on */
this.setState({
e2eStatus: await shieldStatusForRoom(this.context, room),
});
if (this.unmounted) return;
this.setState({ e2eStatus });
}
private onAccountData = (event: MatrixEvent) => {
@ -1137,7 +1140,7 @@ export default class RoomView extends React.Component<IProps, IState> {
if (this.state.searchResults.next_batch) {
debuglog("requesting more search results");
const searchPromise = searchPagination(this.state.searchResults);
const searchPromise = searchPagination(this.state.searchResults as ISearchResults);
return this.handleSearchResult(searchPromise);
} else {
debuglog("no more search results");
@ -1400,7 +1403,7 @@ export default class RoomView extends React.Component<IProps, IState> {
continue;
}
if (!haveTileForEvent(mxEv)) {
if (!haveTileForEvent(mxEv, this.state.showHiddenEventsInTimeline)) {
// XXX: can this ever happen? It will make the result count
// not match the displayed count.
continue;
@ -1753,10 +1756,8 @@ export default class RoomView extends React.Component<IProps, IState> {
}
const myMembership = this.state.room.getMyMembership();
if (myMembership === "invite"
// SpaceRoomView handles invites itself
&& (!SettingsStore.getValue("feature_spaces") || !this.state.room.isSpaceRoom())
) {
// SpaceRoomView handles invites itself
if (myMembership === "invite" && (!SpaceStore.spacesEnabled || !this.state.room.isSpaceRoom())) {
if (this.state.joining || this.state.rejecting) {
return (
<ErrorBoundary>
@ -1887,7 +1888,7 @@ export default class RoomView extends React.Component<IProps, IState> {
room={this.state.room}
/>
);
if (!this.state.canPeek && (!SettingsStore.getValue("feature_spaces") || !this.state.room?.isSpaceRoom())) {
if (!this.state.canPeek && (!SpaceStore.spacesEnabled || !this.state.room?.isSpaceRoom())) {
return (
<div className="mx_RoomView">
{ previewBar }
@ -1901,10 +1902,10 @@ export default class RoomView extends React.Component<IProps, IState> {
className="mx_RoomView_auxPanel_hiddenHighlights"
onClick={this.onHiddenHighlightsClick}
>
{_t(
{ _t(
"You have %(count)s unread notifications in a prior version of this room.",
{ count: hiddenHighlightCount },
)}
) }
</AccessibleButton>
);
}
@ -2016,7 +2017,7 @@ export default class RoomView extends React.Component<IProps, IState> {
onScroll={this.onMessageListScroll}
onUserScroll={this.onUserScroll}
onReadMarkerUpdated={this.updateTopUnreadMessagesBar}
showUrlPreview = {this.state.showUrlPreview}
showUrlPreview={this.state.showUrlPreview}
className={messagePanelClassNames}
membersLoaded={this.state.membersLoaded}
permalinkCreator={this.getPermalinkCreatorForRoom(this.state.room)}
@ -2066,7 +2067,7 @@ export default class RoomView extends React.Component<IProps, IState> {
return (
<RoomContext.Provider value={this.state}>
<main className={mainClasses} ref={this.roomView} onKeyDown={this.onReactKeyDown}>
{showChatEffects && this.roomView.current &&
{ showChatEffects && this.roomView.current &&
<EffectsOverlay roomWidth={this.roomView.current.offsetWidth} />
}
<ErrorBoundary>
@ -2085,22 +2086,22 @@ export default class RoomView extends React.Component<IProps, IState> {
/>
<MainSplit panel={rightPanel} resizeNotifier={this.props.resizeNotifier}>
<div className="mx_RoomView_body">
{auxPanel}
{ auxPanel }
<div className={timelineClasses}>
{fileDropTarget}
{topUnreadMessagesBar}
{jumpToBottom}
{messagePanel}
{searchResultsPanel}
{ fileDropTarget }
{ topUnreadMessagesBar }
{ jumpToBottom }
{ messagePanel }
{ searchResultsPanel }
</div>
<div className={statusBarAreaClass}>
<div className="mx_RoomView_statusAreaBox">
<div className="mx_RoomView_statusAreaBox_line" />
{statusBar}
{ statusBar }
</div>
</div>
{previewBar}
{messageComposer}
{ previewBar }
{ messageComposer }
</div>
</MainSplit>
</ErrorBoundary>

View file

@ -187,7 +187,7 @@ export default class ScrollPanel extends React.Component<IProps> {
private fillRequestWhileRunning: boolean;
private scrollState: IScrollState;
private preventShrinkingState: IPreventShrinkingState;
private unfillDebouncer: NodeJS.Timeout;
private unfillDebouncer: number;
private bottomGrowth: number;
private pages: number;
private heightUpdateInProgress: boolean;

View file

@ -136,7 +136,7 @@ export default class SearchBox extends React.Component {
key="button"
tabIndex={-1}
className="mx_SearchBox_closeButton"
onClick={ () => {this._clearSearch("button"); } }>
onClick={() => {this._clearSearch("button"); }}>
</AccessibleButton>) : undefined;
// show a shorter placeholder when blurred, if requested
@ -153,12 +153,12 @@ export default class SearchBox extends React.Component {
type="text"
ref={this._search}
className={"mx_textinput_icon mx_textinput_search " + className}
value={ this.state.searchTerm }
onFocus={ this._onFocus }
onChange={ this.onChange }
onKeyDown={ this._onKeyDown }
value={this.state.searchTerm}
onFocus={this._onFocus}
onChange={this.onChange}
onKeyDown={this._onKeyDown}
onBlur={this._onBlur}
placeholder={ placeholder }
placeholder={placeholder}
autoComplete="off"
autoFocus={this.props.autoFocus}
/>

View file

@ -18,6 +18,7 @@ import React, { ReactNode, useMemo, useState } from "react";
import { Room } from "matrix-js-sdk/src/models/room";
import { MatrixClient } from "matrix-js-sdk/src/client";
import { EventType, RoomType } from "matrix-js-sdk/src/@types/event";
import { ISpaceSummaryRoom, ISpaceSummaryEvent } from "matrix-js-sdk/src/@types/spaces";
import classNames from "classnames";
import { sortBy } from "lodash";
@ -42,6 +43,7 @@ import { useStateToggle } from "../../hooks/useStateToggle";
import { getChildOrder } from "../../stores/SpaceStore";
import AccessibleTooltipButton from "../views/elements/AccessibleTooltipButton";
import { linkifyElement } from "../../HtmlUtils";
import { getDisplayAliasForAliasSet } from "../../Rooms";
interface IHierarchyProps {
space: Room;
@ -51,36 +53,6 @@ interface IHierarchyProps {
showRoom(room: ISpaceSummaryRoom, viaServers?: string[], autoJoin?: boolean): void;
}
/* eslint-disable camelcase */
export interface ISpaceSummaryRoom {
canonical_alias?: string;
aliases: string[];
avatar_url?: string;
guest_can_join: boolean;
name?: string;
num_joined_members: number;
room_id: string;
topic?: string;
world_readable: boolean;
num_refs: number;
room_type: string;
}
export interface ISpaceSummaryEvent {
room_id: string;
event_id: string;
origin_server_ts: number;
type: string;
state_key: string;
content: {
order?: string;
suggested?: boolean;
auto_join?: boolean;
via?: string[];
};
}
/* eslint-enable camelcase */
interface ITileProps {
room: ISpaceSummaryRoom;
suggested?: boolean;
@ -432,7 +404,7 @@ export const SpaceHierarchy: React.FC<IHierarchyProps> = ({
const [saving, setSaving] = useState(false);
if (summaryError) {
return <p>{_t("Your server does not support showing space hierarchies.")}</p>;
return <p>{ _t("Your server does not support showing space hierarchies.") }</p>;
}
let content;
@ -597,7 +569,7 @@ export const SpaceHierarchy: React.FC<IHierarchyProps> = ({
return <>
<SearchBox
className="mx_textinput_icon mx_textinput_search"
placeholder={ _t("Search names and descriptions") }
placeholder={_t("Search names and descriptions")}
onSearch={setQuery}
autoFocus={true}
initialValue={initialText}
@ -636,7 +608,7 @@ const SpaceRoomDirectory: React.FC<IProps> = ({ space, onFinished, initialText }
{ _t("If you can't find the room you're looking for, ask for an invite or <a>create a new room</a>.",
null,
{ a: sub => {
return <AccessibleButton kind="link" onClick={onCreateRoomClick}>{sub}</AccessibleButton>;
return <AccessibleButton kind="link" onClick={onCreateRoomClick}>{ sub }</AccessibleButton>;
} },
) }
@ -666,5 +638,5 @@ export default SpaceRoomDirectory;
// Similar to matrix-react-sdk's MatrixTools.getDisplayAliasForRoom
// but works with the objects we get from the public room list
function getDisplayAliasForRoom(room: ISpaceSummaryRoom) {
return room.canonical_alias || (room.aliases ? room.aliases[0] : "");
return getDisplayAliasForAliasSet(room.canonical_alias, room.aliases);
}

View file

@ -62,7 +62,6 @@ import IconizedContextMenu, {
import AccessibleTooltipButton from "../views/elements/AccessibleTooltipButton";
import { BetaPill } from "../views/beta/BetaCard";
import { UserTab } from "../views/dialogs/UserSettingsDialog";
import SettingsStore from "../../settings/SettingsStore";
import Modal from "../../Modal";
import BetaFeedbackDialog from "../views/dialogs/BetaFeedbackDialog";
import SdkConfig from "../../SdkConfig";
@ -147,7 +146,7 @@ const SpaceInfo = ({ space }) => {
return <div className="mx_SpaceRoomView_info">
{ visibilitySection }
{ joinRule === "public" && <RoomMemberCount room={space}>
{(count) => count > 0 ? (
{ (count) => count > 0 ? (
<AccessibleButton
kind="link"
onClick={() => {
@ -160,7 +159,7 @@ const SpaceInfo = ({ space }) => {
>
{ _t("%(count)s members", { count }) }
</AccessibleButton>
) : null}
) : null }
</RoomMemberCount> }
</div>;
};
@ -178,7 +177,7 @@ const SpacePreview = ({ space, onJoinButtonClicked, onRejectButtonClicked }) =>
const [busy, setBusy] = useState(false);
const spacesEnabled = SettingsStore.getValue("feature_spaces");
const spacesEnabled = SpaceStore.spacesEnabled;
const cannotJoin = getEffectiveMembership(myMembership) === EffectiveMembership.Leave
&& space.getJoinRule() !== JoinRule.Public;
@ -293,7 +292,7 @@ const SpacePreview = ({ space, onJoinButtonClicked, onRejectButtonClicked }) =>
</h1>
<SpaceInfo space={space} />
<RoomTopic room={space}>
{(topic, ref) =>
{ (topic, ref) =>
<div className="mx_SpaceRoomView_preview_topic" ref={ref}>
{ topic }
</div>
@ -420,12 +419,12 @@ const SpaceLanding = ({ space }) => {
<RoomAvatar room={space} height={80} width={80} viewAvatarOnClick={true} />
<div className="mx_SpaceRoomView_landing_name">
<RoomName room={space}>
{(name) => {
{ (name) => {
const tags = { name: () => <div className="mx_SpaceRoomView_landing_nameRow">
<h1>{ name }</h1>
</div> };
return _t("Welcome to <name/>", {}, tags) as JSX.Element;
}}
} }
</RoomName>
</div>
<div className="mx_SpaceRoomView_landing_info">
@ -435,11 +434,11 @@ const SpaceLanding = ({ space }) => {
{ settingsButton }
</div>
<RoomTopic room={space}>
{(topic, ref) => (
{ (topic, ref) => (
<div className="mx_SpaceRoomView_landing_topic" ref={ref}>
{ topic }
</div>
)}
) }
</RoomTopic>
<SpaceFeedbackPrompt />
<hr />
@ -459,7 +458,7 @@ const SpaceSetupFirstRooms = ({ space, title, description, onFinished }) => {
const numFields = 3;
const placeholders = [_t("General"), _t("Random"), _t("Support")];
const [roomNames, setRoomName] = useStateArray(numFields, [_t("General"), _t("Random"), ""]);
const fields = new Array(numFields).fill(0).map((_, i) => {
const fields = new Array(numFields).fill(0).map((x, i) => {
const name = "roomName" + i;
return <Field
key={name}
@ -626,7 +625,7 @@ const SpaceSetupPrivateInvite = ({ space, onFinished }) => {
const numFields = 3;
const fieldRefs: RefObject<Field>[] = [useRef(), useRef(), useRef()];
const [emailAddresses, setEmailAddress] = useStateArray(numFields, "");
const fields = new Array(numFields).fill(0).map((_, i) => {
const fields = new Array(numFields).fill(0).map((x, i) => {
const name = "emailAddress" + i;
return <Field
key={name}
@ -854,7 +853,7 @@ export default class SpaceRoomView extends React.PureComponent<IProps, IState> {
private renderBody() {
switch (this.state.phase) {
case Phase.Landing:
if (this.state.myMembership === "join" && SettingsStore.getValue("feature_spaces")) {
if (this.state.myMembership === "join" && SpaceStore.spacesEnabled) {
return <SpaceLanding space={this.props.space} />;
} else {
return <SpacePreview

View file

@ -20,6 +20,7 @@ import * as React from "react";
import { _t } from '../../languageHandler';
import AutoHideScrollbar from './AutoHideScrollbar';
import { replaceableComponent } from "../../utils/replaceableComponent";
import classNames from "classnames";
import AccessibleButton from "../views/elements/AccessibleButton";
/**
@ -37,9 +38,16 @@ export class Tab {
}
}
export enum TabLocation {
LEFT = 'left',
TOP = 'top',
}
interface IProps {
tabs: Tab[];
initialTabId?: string;
tabLocation: TabLocation;
onChange?: (tabId: string) => void;
}
interface IState {
@ -62,7 +70,11 @@ export default class TabbedView extends React.Component<IProps, IState> {
};
}
private _getActiveTabIndex() {
static defaultProps = {
tabLocation: TabLocation.LEFT,
};
private getActiveTabIndex() {
if (!this.state || !this.state.activeTabIndex) return 0;
return this.state.activeTabIndex;
}
@ -72,32 +84,33 @@ export default class TabbedView extends React.Component<IProps, IState> {
* @param {Tab} tab the tab to show
* @private
*/
private _setActiveTab(tab: Tab) {
private setActiveTab(tab: Tab) {
const idx = this.props.tabs.indexOf(tab);
if (idx !== -1) {
if (this.props.onChange) this.props.onChange(tab.id);
this.setState({ activeTabIndex: idx });
} else {
console.error("Could not find tab " + tab.label + " in tabs");
}
}
private _renderTabLabel(tab: Tab) {
private renderTabLabel(tab: Tab) {
let classes = "mx_TabbedView_tabLabel ";
const idx = this.props.tabs.indexOf(tab);
if (idx === this._getActiveTabIndex()) classes += "mx_TabbedView_tabLabel_active";
if (idx === this.getActiveTabIndex()) classes += "mx_TabbedView_tabLabel_active";
let tabIcon = null;
if (tab.icon) {
tabIcon = <span className={`mx_TabbedView_maskedIcon ${tab.icon}`} />;
}
const onClickHandler = () => this._setActiveTab(tab);
const onClickHandler = () => this.setActiveTab(tab);
const label = _t(tab.label);
return (
<AccessibleButton className={classes} key={"tab_label_" + tab.label} onClick={onClickHandler}>
{tabIcon}
{ tabIcon }
<span className="mx_TabbedView_tabLabel_text">
{ label }
</span>
@ -105,26 +118,32 @@ export default class TabbedView extends React.Component<IProps, IState> {
);
}
private _renderTabPanel(tab: Tab): React.ReactNode {
private renderTabPanel(tab: Tab): React.ReactNode {
return (
<div className="mx_TabbedView_tabPanel" key={"mx_tabpanel_" + tab.label}>
<AutoHideScrollbar className='mx_TabbedView_tabPanelContent'>
{tab.body}
{ tab.body }
</AutoHideScrollbar>
</div>
);
}
public render(): React.ReactNode {
const labels = this.props.tabs.map(tab => this._renderTabLabel(tab));
const panel = this._renderTabPanel(this.props.tabs[this._getActiveTabIndex()]);
const labels = this.props.tabs.map(tab => this.renderTabLabel(tab));
const panel = this.renderTabPanel(this.props.tabs[this.getActiveTabIndex()]);
const tabbedViewClasses = classNames({
'mx_TabbedView': true,
'mx_TabbedView_tabsOnLeft': this.props.tabLocation == TabLocation.LEFT,
'mx_TabbedView_tabsOnTop': this.props.tabLocation == TabLocation.TOP,
});
return (
<div className="mx_TabbedView">
<div className={tabbedViewClasses}>
<div className="mx_TabbedView_tabLabels">
{labels}
{ labels }
</div>
{panel}
{ panel }
</div>
);
}

View file

@ -277,7 +277,7 @@ class TimelinePanel extends React.Component<IProps, IState> {
}
// TODO: [REACT-WARNING] Move into constructor
// eslint-disable-next-line camelcase
// eslint-disable-next-line
UNSAFE_componentWillMount() {
if (this.props.manageReadReceipts) {
this.updateReadReceiptOnUserActivity();
@ -290,7 +290,7 @@ class TimelinePanel extends React.Component<IProps, IState> {
}
// TODO: [REACT-WARNING] Replace with appropriate lifecycle event
// eslint-disable-next-line camelcase
// eslint-disable-next-line
UNSAFE_componentWillReceiveProps(newProps) {
if (newProps.timelineSet !== this.props.timelineSet) {
// throw new Error("changing timelineSet on a TimelinePanel is not supported");
@ -555,9 +555,8 @@ class TimelinePanel extends React.Component<IProps, IState> {
// more than the timeout on userActiveRecently.
//
const myUserId = MatrixClientPeg.get().credentials.userId;
const sender = ev.sender ? ev.sender.userId : null;
callRMUpdated = false;
if (sender != myUserId && !UserActivity.sharedInstance().userActiveRecently()) {
if (ev.getSender() !== myUserId && !UserActivity.sharedInstance().userActiveRecently()) {
updatedState.readMarkerVisible = true;
} else if (lastLiveEvent && this.getReadMarkerPosition() === 0) {
// we know we're stuckAtBottom, so we can advance the RM
@ -666,8 +665,8 @@ class TimelinePanel extends React.Component<IProps, IState> {
private readMarkerTimeout(readMarkerPosition: number): number {
return readMarkerPosition === 0 ?
this.state.readMarkerInViewThresholdMs :
this.state.readMarkerOutOfViewThresholdMs;
this.context?.readMarkerInViewThresholdMs ?? this.state.readMarkerInViewThresholdMs :
this.context?.readMarkerOutOfViewThresholdMs ?? this.state.readMarkerOutOfViewThresholdMs;
}
private async updateReadMarkerOnUserActivity(): Promise<void> {
@ -863,7 +862,7 @@ class TimelinePanel extends React.Component<IProps, IState> {
const myUserId = MatrixClientPeg.get().credentials.userId;
for (i++; i < events.length; i++) {
const ev = events[i];
if (!ev.sender || ev.sender.userId != myUserId) {
if (ev.getSender() !== myUserId) {
break;
}
}
@ -1051,6 +1050,8 @@ class TimelinePanel extends React.Component<IProps, IState> {
{ windowLimit: this.props.timelineCap });
const onLoaded = () => {
if (this.unmounted) return;
// clear the timeline min-height when
// (re)loading the timeline
if (this.messagePanel.current) {
@ -1092,6 +1093,8 @@ class TimelinePanel extends React.Component<IProps, IState> {
};
const onError = (error) => {
if (this.unmounted) return;
this.setState({ timelineLoading: false });
console.error(
`Error loading timeline panel at ${eventId}: ${error}`,
@ -1333,8 +1336,9 @@ class TimelinePanel extends React.Component<IProps, IState> {
}
const shouldIgnore = !!ev.status || // local echo
(ignoreOwn && ev.sender && ev.sender.userId == myUserId); // own message
const isWithoutTile = !haveTileForEvent(ev) || shouldHideEvent(ev, this.context);
(ignoreOwn && ev.getSender() === myUserId); // own message
const isWithoutTile = !haveTileForEvent(ev, this.context?.showHiddenEventsInTimeline) ||
shouldHideEvent(ev, this.context);
if (isWithoutTile || !node) {
// don't start counting if the event should be ignored,
@ -1444,7 +1448,7 @@ class TimelinePanel extends React.Component<IProps, IState> {
if (this.state.events.length == 0 && !this.state.canBackPaginate && this.props.empty) {
return (
<div className={this.props.className + " mx_RoomView_messageListWrapper"}>
<div className="mx_RoomView_empty">{this.props.empty}</div>
<div className="mx_RoomView_empty">{ this.props.empty }</div>
</div>
);
}
@ -1489,8 +1493,12 @@ class TimelinePanel extends React.Component<IProps, IState> {
onUserScroll={this.props.onUserScroll}
onFillRequest={this.onMessageListFillRequest}
onUnfillRequest={this.onMessageListUnfillRequest}
isTwelveHour={this.state.isTwelveHour}
alwaysShowTimestamps={this.props.alwaysShowTimestamps || this.state.alwaysShowTimestamps}
isTwelveHour={this.context?.showTwelveHourTimestamps ?? this.state.isTwelveHour}
alwaysShowTimestamps={
this.props.alwaysShowTimestamps ??
this.context?.alwaysShowTimestamps ??
this.state.alwaysShowTimestamps
}
className={this.props.className}
tileShape={this.props.tileShape}
resizeNotifier={this.props.resizeNotifier}

View file

@ -37,14 +37,14 @@ export default class ToastContainer extends React.Component<{}, IState> {
// toasts may dismiss themselves in their didMount if they find
// they're already irrelevant by the time they're mounted, and
// our own componentDidMount is too late.
ToastStore.sharedInstance().on('update', this._onToastStoreUpdate);
ToastStore.sharedInstance().on('update', this.onToastStoreUpdate);
}
componentWillUnmount() {
ToastStore.sharedInstance().removeListener('update', this._onToastStoreUpdate);
ToastStore.sharedInstance().removeListener('update', this.onToastStoreUpdate);
}
_onToastStoreUpdate = () => {
private onToastStoreUpdate = () => {
this.setState({
toasts: ToastStore.sharedInstance().getToasts(),
countSeen: ToastStore.sharedInstance().getCountSeen(),
@ -75,10 +75,10 @@ export default class ToastContainer extends React.Component<{}, IState> {
});
toast = (<div className={toastClasses}>
<div className="mx_Toast_title">
<h2>{title}</h2>
<span>{countIndicator}</span>
<h2>{ title }</h2>
<span>{ countIndicator }</span>
</div>
<div className="mx_Toast_body">{React.createElement(component, toastProps)}</div>
<div className="mx_Toast_body">{ React.createElement(component, toastProps) }</div>
</div>);
containerClasses = classNames("mx_ToastContainer", {
@ -88,7 +88,7 @@ export default class ToastContainer extends React.Component<{}, IState> {
return toast
? (
<div className={containerClasses} role="alert">
{toast}
{ toast }
</div>
)
: null;

View file

@ -104,7 +104,7 @@ export default class UploadBar extends React.Component<IProps, IState> {
const uploadSize = filesize(this.state.currentUpload.total);
return (
<div className="mx_UploadBar">
<div className="mx_UploadBar_filename">{uploadText} ({uploadSize})</div>
<div className="mx_UploadBar_filename">{ uploadText } ({ uploadSize })</div>
<AccessibleButton onClick={this.onCancelClick} className='mx_UploadBar_cancel' />
<ProgressBar value={this.state.currentUpload.loaded} max={this.state.currentUpload.total} />
</div>

View file

@ -90,7 +90,7 @@ export default class UserMenu extends React.Component<IProps, IState> {
};
OwnProfileStore.instance.on(UPDATE_EVENT, this.onProfileUpdate);
if (SettingsStore.getValue("feature_spaces")) {
if (SpaceStore.spacesEnabled) {
SpaceStore.instance.on(UPDATE_SELECTED_SPACE, this.onSelectedSpaceUpdate);
}
@ -115,7 +115,7 @@ export default class UserMenu extends React.Component<IProps, IState> {
if (this.dispatcherRef) defaultDispatcher.unregister(this.dispatcherRef);
OwnProfileStore.instance.off(UPDATE_EVENT, this.onProfileUpdate);
this.tagStoreRef.remove();
if (SettingsStore.getValue("feature_spaces")) {
if (SpaceStore.spacesEnabled) {
SpaceStore.instance.off(UPDATE_SELECTED_SPACE, this.onSelectedSpaceUpdate);
}
MatrixClientPeg.get().removeListener("Room", this.onRoom);
@ -342,20 +342,20 @@ export default class UserMenu extends React.Component<IProps, IState> {
if (MatrixClientPeg.get().isGuest()) {
topSection = (
<div className="mx_UserMenu_contextMenu_header mx_UserMenu_contextMenu_guestPrompts">
{_t("Got an account? <a>Sign in</a>", {}, {
{ _t("Got an account? <a>Sign in</a>", {}, {
a: sub => (
<AccessibleButton kind="link" onClick={this.onSignInClick}>
{sub}
{ sub }
</AccessibleButton>
),
})}
{_t("New here? <a>Create an account</a>", {}, {
}) }
{ _t("New here? <a>Create an account</a>", {}, {
a: sub => (
<AccessibleButton kind="link" onClick={this.onRegisterClick}>
{sub}
{ sub }
</AccessibleButton>
),
})}
}) }
</div>
);
} else if (hostSignupConfig) {
@ -394,17 +394,17 @@ export default class UserMenu extends React.Component<IProps, IState> {
let primaryHeader = (
<div className="mx_UserMenu_contextMenu_name">
<span className="mx_UserMenu_contextMenu_displayName">
{OwnProfileStore.instance.displayName}
{ OwnProfileStore.instance.displayName }
</span>
<span className="mx_UserMenu_contextMenu_userId">
{MatrixClientPeg.get().getUserId()}
{ MatrixClientPeg.get().getUserId() }
</span>
</div>
);
let primaryOptionList = (
<React.Fragment>
<IconizedContextMenuOptionList>
{homeButton}
{ homeButton }
<IconizedContextMenuOption
iconClassName="mx_UserMenu_iconBell"
label={_t("Notification settings")}
@ -420,11 +420,11 @@ export default class UserMenu extends React.Component<IProps, IState> {
label={_t("All settings")}
onClick={(e) => this.onSettingsOpen(e, null)}
/>
{/* <IconizedContextMenuOption
{ /* <IconizedContextMenuOption
iconClassName="mx_UserMenu_iconArchive"
label={_t("Archived rooms")}
onClick={this.onShowArchived}
/> */}
/> */ }
{ feedbackButton }
</IconizedContextMenuOptionList>
<IconizedContextMenuOptionList red>
@ -443,7 +443,7 @@ export default class UserMenu extends React.Component<IProps, IState> {
primaryHeader = (
<div className="mx_UserMenu_contextMenu_name">
<span className="mx_UserMenu_contextMenu_displayName">
{prototypeCommunityName}
{ prototypeCommunityName }
</span>
</div>
);
@ -470,13 +470,13 @@ export default class UserMenu extends React.Component<IProps, IState> {
}
primaryOptionList = (
<IconizedContextMenuOptionList>
{settingsOption}
{ settingsOption }
<IconizedContextMenuOption
iconClassName="mx_UserMenu_iconMembers"
label={_t("Members")}
onClick={this.onCommunityMembersClick}
/>
{inviteOption}
{ inviteOption }
</IconizedContextMenuOptionList>
);
secondarySection = (
@ -485,10 +485,10 @@ export default class UserMenu extends React.Component<IProps, IState> {
<div className="mx_UserMenu_contextMenu_header">
<div className="mx_UserMenu_contextMenu_name">
<span className="mx_UserMenu_contextMenu_displayName">
{OwnProfileStore.instance.displayName}
{ OwnProfileStore.instance.displayName }
</span>
<span className="mx_UserMenu_contextMenu_userId">
{MatrixClientPeg.get().getUserId()}
{ MatrixClientPeg.get().getUserId() }
</span>
</div>
</div>
@ -540,7 +540,7 @@ export default class UserMenu extends React.Component<IProps, IState> {
className={classes}
>
<div className="mx_UserMenu_contextMenu_header">
{primaryHeader}
{ primaryHeader }
<AccessibleTooltipButton
className="mx_UserMenu_contextMenu_themeButton"
onClick={this.onSwitchThemeClick}
@ -553,9 +553,9 @@ export default class UserMenu extends React.Component<IProps, IState> {
/>
</AccessibleTooltipButton>
</div>
{topSection}
{primaryOptionList}
{secondarySection}
{ topSection }
{ primaryOptionList }
{ secondarySection }
</IconizedContextMenu>;
};
@ -570,27 +570,27 @@ export default class UserMenu extends React.Component<IProps, IState> {
let isPrototype = false;
let menuName = _t("User menu");
let name = <span className="mx_UserMenu_userName">{displayName}</span>;
let name = <span className="mx_UserMenu_userName">{ displayName }</span>;
let buttons = (
<span className="mx_UserMenu_headerButtons">
{/* masked image in CSS */}
{ /* masked image in CSS */ }
</span>
);
let dnd;
if (this.state.selectedSpace) {
name = (
<div className="mx_UserMenu_doubleName">
<span className="mx_UserMenu_userName">{displayName}</span>
<span className="mx_UserMenu_userName">{ displayName }</span>
<RoomName room={this.state.selectedSpace}>
{(roomName) => <span className="mx_UserMenu_subUserName">{roomName}</span>}
{ (roomName) => <span className="mx_UserMenu_subUserName">{ roomName }</span> }
</RoomName>
</div>
);
} else if (prototypeCommunityName) {
name = (
<div className="mx_UserMenu_doubleName">
<span className="mx_UserMenu_userName">{prototypeCommunityName}</span>
<span className="mx_UserMenu_subUserName">{displayName}</span>
<span className="mx_UserMenu_userName">{ prototypeCommunityName }</span>
<span className="mx_UserMenu_subUserName">{ displayName }</span>
</div>
);
menuName = _t("Community and user menu");
@ -598,8 +598,8 @@ export default class UserMenu extends React.Component<IProps, IState> {
} else if (SettingsStore.getValue("feature_communities_v2_prototypes")) {
name = (
<div className="mx_UserMenu_doubleName">
<span className="mx_UserMenu_userName">{_t("Home")}</span>
<span className="mx_UserMenu_subUserName">{displayName}</span>
<span className="mx_UserMenu_userName">{ _t("Home") }</span>
<span className="mx_UserMenu_subUserName">{ displayName }</span>
</div>
);
isPrototype = true;
@ -647,20 +647,20 @@ export default class UserMenu extends React.Component<IProps, IState> {
className="mx_UserMenu_userAvatar"
/>
</span>
{name}
{this.state.pendingRoomJoin.size > 0 && (
{ name }
{ this.state.pendingRoomJoin.size > 0 && (
<InlineSpinner>
<TooltipButton helpText={_t(
"Currently joining %(count)s rooms",
{ count: this.state.pendingRoomJoin.size },
)} />
</InlineSpinner>
)}
{dnd}
{buttons}
) }
{ dnd }
{ buttons }
</div>
</ContextMenuButton>
{this.renderContextMenu()}
{ this.renderContextMenu() }
</React.Fragment>
);
}

View file

@ -63,23 +63,23 @@ export default class ViewSource extends React.Component {
<>
<details open className="mx_ViewSource_details">
<summary>
<span className="mx_ViewSource_heading">{_t("Decrypted event source")}</span>
<span className="mx_ViewSource_heading">{ _t("Decrypted event source") }</span>
</summary>
<SyntaxHighlight className="json">{JSON.stringify(decryptedEventSource, null, 2)}</SyntaxHighlight>
<SyntaxHighlight className="json">{ JSON.stringify(decryptedEventSource, null, 2) }</SyntaxHighlight>
</details>
<details className="mx_ViewSource_details">
<summary>
<span className="mx_ViewSource_heading">{_t("Original event source")}</span>
<span className="mx_ViewSource_heading">{ _t("Original event source") }</span>
</summary>
<SyntaxHighlight className="json">{JSON.stringify(originalEventSource, null, 2)}</SyntaxHighlight>
<SyntaxHighlight className="json">{ JSON.stringify(originalEventSource, null, 2) }</SyntaxHighlight>
</details>
</>
);
} else {
return (
<>
<div className="mx_ViewSource_heading">{_t("Original event source")}</div>
<SyntaxHighlight className="json">{JSON.stringify(originalEventSource, null, 2)}</SyntaxHighlight>
<div className="mx_ViewSource_heading">{ _t("Original event source") }</div>
<SyntaxHighlight className="json">{ JSON.stringify(originalEventSource, null, 2) }</SyntaxHighlight>
</>
);
}
@ -110,7 +110,7 @@ export default class ViewSource extends React.Component {
if (isStateEvent) {
return (
<MatrixClientContext.Consumer>
{(cli) => (
{ (cli) => (
<SendCustomEvent
room={cli.getRoom(roomId)}
forceStateEvent={true}
@ -121,7 +121,7 @@ export default class ViewSource extends React.Component {
stateKey: mxEvent.getStateKey(),
}}
/>
)}
) }
</MatrixClientContext.Consumer>
);
} else {
@ -142,7 +142,7 @@ export default class ViewSource extends React.Component {
};
return (
<MatrixClientContext.Consumer>
{(cli) => (
{ (cli) => (
<SendCustomEvent
room={cli.getRoom(roomId)}
forceStateEvent={false}
@ -153,7 +153,7 @@ export default class ViewSource extends React.Component {
evContent: JSON.stringify(newContent, null, "\t"),
}}
/>
)}
) }
</MatrixClientContext.Consumer>
);
}
@ -176,16 +176,16 @@ export default class ViewSource extends React.Component {
return (
<BaseDialog className="mx_ViewSource" onFinished={this.props.onFinished} title={_t("View Source")}>
<div>
<div>Room ID: {roomId}</div>
<div>Event ID: {eventId}</div>
<div>Room ID: { roomId }</div>
<div>Event ID: { eventId }</div>
<div className="mx_ViewSource_separator" />
{isEditing ? this.editSourceContent() : this.viewSourceContent()}
{ isEditing ? this.editSourceContent() : this.viewSourceContent() }
</div>
{!isEditing && canEdit && (
{ !isEditing && canEdit && (
<div className="mx_Dialog_buttons">
<button onClick={() => this.onEdit()}>{_t("Edit")}</button>
<button onClick={() => this.onEdit()}>{ _t("Edit") }</button>
</div>
)}
) }
</BaseDialog>
);
}

View file

@ -79,8 +79,8 @@ export default class CompleteSecurity extends React.Component<IProps, IState> {
<AuthPage>
<CompleteSecurityBody>
<h2 className="mx_CompleteSecurity_header">
{icon}
{title}
{ icon }
{ title }
</h2>
<div className="mx_CompleteSecurity_body">
<SetupEncryptionBody onFinished={this.props.onFinished} />

View file

@ -101,7 +101,7 @@ export default class ForgotPassword extends React.Component<IProps, IState> {
}
// TODO: [REACT-WARNING] Replace with appropriate lifecycle event
// eslint-disable-next-line camelcase
// eslint-disable-next-line
public UNSAFE_componentWillReceiveProps(newProps: IProps): void {
if (newProps.serverConfig.hsUrl === this.props.serverConfig.hsUrl &&
newProps.serverConfig.isUrl === this.props.serverConfig.isUrl) return;
@ -239,14 +239,14 @@ export default class ForgotPassword extends React.Component<IProps, IState> {
});
serverDeadSection = (
<div className={classes}>
{this.state.serverDeadError}
{ this.state.serverDeadError }
</div>
);
}
return <div>
{errorText}
{serverDeadSection}
{ errorText }
{ serverDeadSection }
<ServerPicker
serverConfig={this.props.serverConfig}
onServerConfigChange={this.props.onServerConfigChange}
@ -289,10 +289,10 @@ export default class ForgotPassword extends React.Component<IProps, IState> {
autoComplete="new-password"
/>
</div>
<span>{_t(
<span>{ _t(
'A verification email will be sent to your inbox to confirm ' +
'setting your new password.',
)}</span>
) }</span>
<input
className="mx_Login_submit"
type="submit"
@ -300,7 +300,7 @@ export default class ForgotPassword extends React.Component<IProps, IState> {
/>
</form>
<a className="mx_AuthBody_changeFlow" onClick={this.onLoginClick} href="#">
{_t('Sign in instead')}
{ _t('Sign in instead') }
</a>
</div>;
}
@ -312,8 +312,8 @@ export default class ForgotPassword extends React.Component<IProps, IState> {
renderEmailSent() {
return <div>
{_t("An email has been sent to %(emailAddress)s. Once you've followed the " +
"link it contains, click below.", { emailAddress: this.state.email })}
{ _t("An email has been sent to %(emailAddress)s. Once you've followed the " +
"link it contains, click below.", { emailAddress: this.state.email }) }
<br />
<input className="mx_Login_submit" type="button" onClick={this.onVerify}
value={_t('I have verified my email address')} />
@ -322,12 +322,12 @@ export default class ForgotPassword extends React.Component<IProps, IState> {
renderDone() {
return <div>
<p>{_t("Your password has been reset.")}</p>
<p>{_t(
<p>{ _t("Your password has been reset.") }</p>
<p>{ _t(
"You have been logged out of all sessions and will no longer receive " +
"push notifications. To re-enable notifications, sign in again on each " +
"device.",
)}</p>
) }</p>
<input className="mx_Login_submit" type="button" onClick={this.props.onComplete}
value={_t('Return to login screen')} />
</div>;
@ -358,7 +358,7 @@ export default class ForgotPassword extends React.Component<IProps, IState> {
<AuthHeader />
<AuthBody>
<h2> { _t('Set a new password') } </h2>
{resetPasswordJsx}
{ resetPasswordJsx }
</AuthBody>
</AuthPage>
);

View file

@ -144,7 +144,7 @@ export default class LoginComponent extends React.PureComponent<IProps, IState>
}
// TODO: [REACT-WARNING] Replace with appropriate lifecycle event
// eslint-disable-next-line camelcase
// eslint-disable-next-line
UNSAFE_componentWillMount() {
this.initLoginLogic(this.props.serverConfig);
}
@ -154,7 +154,7 @@ export default class LoginComponent extends React.PureComponent<IProps, IState>
}
// TODO: [REACT-WARNING] Replace with appropriate lifecycle event
// eslint-disable-next-line camelcase
// eslint-disable-next-line
UNSAFE_componentWillReceiveProps(newProps) {
if (newProps.serverConfig.hsUrl === this.props.serverConfig.hsUrl &&
newProps.serverConfig.isUrl === this.props.serverConfig.isUrl) return;
@ -239,8 +239,8 @@ export default class LoginComponent extends React.PureComponent<IProps, IState>
);
errorText = (
<div>
<div>{errorTop}</div>
<div className="mx_Login_smallError">{errorDetail}</div>
<div>{ errorTop }</div>
<div className="mx_Login_smallError">{ errorDetail }</div>
</div>
);
} else if (error.httpStatus === 401 || error.httpStatus === 403) {
@ -251,10 +251,10 @@ export default class LoginComponent extends React.PureComponent<IProps, IState>
<div>
<div>{ _t('Incorrect username and/or password.') }</div>
<div className="mx_Login_smallError">
{_t(
{ _t(
'Please note you are logging into the %(hs)s server, not matrix.org.',
{ hs: this.props.serverConfig.hsName },
)}
) }
</div>
</div>
);
@ -565,7 +565,7 @@ export default class LoginComponent extends React.PureComponent<IProps, IState>
});
serverDeadSection = (
<div className={classes}>
{this.state.serverDeadError}
{ this.state.serverDeadError }
</div>
);
}
@ -578,15 +578,15 @@ export default class LoginComponent extends React.PureComponent<IProps, IState>
{ this.props.isSyncing ? _t("Syncing...") : _t("Signing In...") }
</div>
{ this.props.isSyncing && <div className="mx_AuthBody_paddedFooter_subtitle">
{_t("If you've joined lots of rooms, this might take a while")}
{ _t("If you've joined lots of rooms, this might take a while") }
</div> }
</div>;
} else if (SettingsStore.getValue(UIFeature.Registration)) {
footer = (
<span className="mx_AuthBody_changeFlow">
{_t("New? <a>Create account</a>", {}, {
{ _t("New? <a>Create account</a>", {}, {
a: sub => <a onClick={this.onTryRegisterClick} href="#">{ sub }</a>,
})}
}) }
</span>
);
}
@ -596,8 +596,8 @@ export default class LoginComponent extends React.PureComponent<IProps, IState>
<AuthHeader disableLanguageSelector={this.props.isSyncing || this.state.busyLoggingIn} />
<AuthBody>
<h2>
{_t('Sign in')}
{loader}
{ _t('Sign in') }
{ loader }
</h2>
{ errorTextSection }
{ serverDeadSection }

View file

@ -141,7 +141,7 @@ export default class Registration extends React.Component<IProps, IState> {
}
// TODO: [REACT-WARNING] Replace with appropriate lifecycle event
// eslint-disable-next-line camelcase
// eslint-disable-next-line
UNSAFE_componentWillReceiveProps(newProps) {
if (newProps.serverConfig.hsUrl === this.props.serverConfig.hsUrl &&
newProps.serverConfig.isUrl === this.props.serverConfig.isUrl) return;
@ -290,8 +290,8 @@ export default class Registration extends React.Component<IProps, IState> {
},
);
msg = <div>
<p>{errorTop}</p>
<p>{errorDetail}</p>
<p>{ errorTop }</p>
<p>{ errorDetail }</p>
</div>;
} else if (response.required_stages && response.required_stages.indexOf('m.login.msisdn') > -1) {
let msisdnAvailable = false;
@ -482,13 +482,13 @@ export default class Registration extends React.Component<IProps, IState> {
fragmentAfterLogin={this.props.fragmentAfterLogin}
/>
<h3 className="mx_AuthBody_centered">
{_t(
{ _t(
"%(ssoButtons)s Or %(usernamePassword)s",
{
ssoButtons: "",
usernamePassword: "",
},
).trim()}
).trim() }
</h3>
</React.Fragment>;
}
@ -526,15 +526,15 @@ export default class Registration extends React.Component<IProps, IState> {
});
serverDeadSection = (
<div className={classes}>
{this.state.serverDeadError}
{ this.state.serverDeadError }
</div>
);
}
const signIn = <span className="mx_AuthBody_changeFlow">
{_t("Already have an account? <a>Sign in here</a>", {}, {
{ _t("Already have an account? <a>Sign in here</a>", {}, {
a: sub => <a onClick={this.onLoginClick} href="#">{ sub }</a>,
})}
}) }
</span>;
// Only show the 'go back' button if you're not looking at the form
@ -550,43 +550,43 @@ export default class Registration extends React.Component<IProps, IState> {
let regDoneText;
if (this.state.differentLoggedInUserId) {
regDoneText = <div>
<p>{_t(
<p>{ _t(
"Your new account (%(newAccountId)s) is registered, but you're already " +
"logged into a different account (%(loggedInUserId)s).", {
newAccountId: this.state.registeredUsername,
loggedInUserId: this.state.differentLoggedInUserId,
},
)}</p>
) }</p>
<p><AccessibleButton element="span" className="mx_linkButton" onClick={async event => {
const sessionLoaded = await this.onLoginClickWithCheck(event);
if (sessionLoaded) {
dis.dispatch({ action: "view_welcome_page" });
}
}}>
{_t("Continue with previous account")}
{ _t("Continue with previous account") }
</AccessibleButton></p>
</div>;
} else if (this.state.formVals.password) {
// We're the client that started the registration
regDoneText = <h3>{_t(
regDoneText = <h3>{ _t(
"<a>Log in</a> to your new account.", {},
{
a: (sub) => <a href="#/login" onClick={this.onLoginClickWithCheck}>{sub}</a>,
a: (sub) => <a href="#/login" onClick={this.onLoginClickWithCheck}>{ sub }</a>,
},
)}</h3>;
) }</h3>;
} else {
// We're not the original client: the user probably got to us by clicking the
// email validation link. We can't offer a 'go straight to your account' link
// as we don't have the original creds.
regDoneText = <h3>{_t(
regDoneText = <h3>{ _t(
"You can now close this window or <a>log in</a> to your new account.", {},
{
a: (sub) => <a href="#/login" onClick={this.onLoginClickWithCheck}>{sub}</a>,
a: (sub) => <a href="#/login" onClick={this.onLoginClickWithCheck}>{ sub }</a>,
},
)}</h3>;
) }</h3>;
}
body = <div>
<h2>{_t("Registration Successful")}</h2>
<h2>{ _t("Registration Successful") }</h2>
{ regDoneText }
</div>;
} else {

View file

@ -152,7 +152,7 @@ export default class SetupEncryptionBody extends React.Component<IProps, IState>
let useRecoveryKeyButton;
if (recoveryKeyPrompt) {
useRecoveryKeyButton = <AccessibleButton kind="link" onClick={this.onUsePassphraseClick}>
{recoveryKeyPrompt}
{ recoveryKeyPrompt }
</AccessibleButton>;
}
@ -165,15 +165,15 @@ export default class SetupEncryptionBody extends React.Component<IProps, IState>
return (
<div>
<p>{_t(
<p>{ _t(
"Verify your identity to access encrypted messages and prove your identity to others.",
)}</p>
) }</p>
<div className="mx_CompleteSecurity_actionRow">
{verifyButton}
{useRecoveryKeyButton}
{ verifyButton }
{ useRecoveryKeyButton }
<AccessibleButton kind="danger" onClick={this.onSkipClick}>
{_t("Skip")}
{ _t("Skip") }
</AccessibleButton>
</div>
</div>
@ -181,25 +181,25 @@ export default class SetupEncryptionBody extends React.Component<IProps, IState>
} else if (phase === Phase.Done) {
let message;
if (this.state.backupInfo) {
message = <p>{_t(
message = <p>{ _t(
"Your new session is now verified. It has access to your " +
"encrypted messages, and other users will see it as trusted.",
)}</p>;
) }</p>;
} else {
message = <p>{_t(
message = <p>{ _t(
"Your new session is now verified. Other users will see it as trusted.",
)}</p>;
) }</p>;
}
return (
<div>
<div className="mx_CompleteSecurity_heroIcon mx_E2EIcon_verified" />
{message}
{ message }
<div className="mx_CompleteSecurity_actionRow">
<AccessibleButton
kind="primary"
onClick={this.onDoneClick}
>
{_t("Done")}
{ _t("Done") }
</AccessibleButton>
</div>
</div>
@ -207,23 +207,23 @@ export default class SetupEncryptionBody extends React.Component<IProps, IState>
} else if (phase === Phase.ConfirmSkip) {
return (
<div>
<p>{_t(
<p>{ _t(
"Without verifying, you wont have access to all your messages " +
"and may appear as untrusted to others.",
)}</p>
) }</p>
<div className="mx_CompleteSecurity_actionRow">
<AccessibleButton
className="warning"
kind="secondary"
onClick={this.onSkipConfirmClick}
>
{_t("Skip")}
{ _t("Skip") }
</AccessibleButton>
<AccessibleButton
kind="danger"
onClick={this.onSkipBackClick}
>
{_t("Go Back")}
{ _t("Go Back") }
</AccessibleButton>
</div>
</div>

View file

@ -219,7 +219,7 @@ export default class SoftLogout extends React.Component<IProps, IState> {
if (this.state.loginView === LOGIN_VIEW.PASSWORD) {
let error = null;
if (this.state.errorText) {
error = <span className='mx_Login_error'>{this.state.errorText}</span>;
error = <span className='mx_Login_error'>{ this.state.errorText }</span>;
}
if (!introText) {
@ -228,8 +228,8 @@ export default class SoftLogout extends React.Component<IProps, IState> {
return (
<form onSubmit={this.onPasswordLogin}>
<p>{introText}</p>
{error}
<p>{ introText }</p>
{ error }
<Field
type="password"
label={_t("Password")}
@ -243,10 +243,10 @@ export default class SoftLogout extends React.Component<IProps, IState> {
type="submit"
disabled={this.state.busy}
>
{_t("Sign In")}
{ _t("Sign In") }
</AccessibleButton>
<AccessibleButton onClick={this.onForgotPassword} kind="link">
{_t("Forgotten your password?")}
{ _t("Forgotten your password?") }
</AccessibleButton>
</form>
);
@ -262,7 +262,7 @@ export default class SoftLogout extends React.Component<IProps, IState> {
return (
<div>
<p>{introText}</p>
<p>{ introText }</p>
<SSOButtons
matrixClient={MatrixClientPeg.get()}
flow={flow}
@ -277,10 +277,10 @@ export default class SoftLogout extends React.Component<IProps, IState> {
// Default: assume unsupported/error
return (
<p>
{_t(
{ _t(
"You cannot sign in to your account. Please contact your " +
"homeserver admin for more information.",
)}
) }
</p>
);
}
@ -291,25 +291,25 @@ export default class SoftLogout extends React.Component<IProps, IState> {
<AuthHeader />
<AuthBody>
<h2>
{_t("You're signed out")}
{ _t("You're signed out") }
</h2>
<h3>{_t("Sign in")}</h3>
<h3>{ _t("Sign in") }</h3>
<div>
{this.renderSignInSection()}
{ this.renderSignInSection() }
</div>
<h3>{_t("Clear personal data")}</h3>
<h3>{ _t("Clear personal data") }</h3>
<p>
{_t(
{ _t(
"Warning: Your personal data (including encryption keys) is still stored " +
"in this session. Clear it if you're finished using this session, or want to sign " +
"in to another account.",
)}
) }
</p>
<div>
<AccessibleButton onClick={this.onClearAll} kind="danger">
{_t("Clear all data")}
{ _t("Clear all data") }
</AccessibleButton>
</div>
</AuthBody>

View file

@ -101,11 +101,11 @@ export default class AudioPlayer extends React.PureComponent<IProps, IState> {
/>
<div className='mx_AudioPlayer_mediaInfo'>
<span className='mx_AudioPlayer_mediaName'>
{this.props.mediaName || _t("Unnamed audio")}
{ this.props.mediaName || _t("Unnamed audio") }
</span>
<div className='mx_AudioPlayer_byline'>
<DurationClock playback={this.props.playback} />
&nbsp; {/* easiest way to introduce a gap between the components */}
&nbsp; { /* easiest way to introduce a gap between the components */ }
{ this.renderFileSize() }
</div>
</div>

View file

@ -43,6 +43,6 @@ export default class Clock extends React.Component<IProps, IState> {
public render() {
const minutes = Math.floor(this.props.seconds / 60).toFixed(0).padStart(2, '0');
const seconds = Math.floor(this.props.seconds % 60).toFixed(0).padStart(2, '0'); // hide millis
return <span className='mx_Clock'>{minutes}:{seconds}</span>;
return <span className='mx_Clock'>{ minutes }:{ seconds }</span>;
}
}

View file

@ -17,15 +17,18 @@ limitations under the License.
import { Playback, PlaybackState } from "../../../voice/Playback";
import React, { ReactNode } from "react";
import { UPDATE_EVENT } from "../../../stores/AsyncStore";
import PlaybackWaveform from "./PlaybackWaveform";
import PlayPauseButton from "./PlayPauseButton";
import PlaybackClock from "./PlaybackClock";
import { replaceableComponent } from "../../../utils/replaceableComponent";
import { TileShape } from "../rooms/EventTile";
import PlaybackWaveform from "./PlaybackWaveform";
interface IProps {
// Playback instance to render. Cannot change during component lifecycle: create
// an all-new component instead.
playback: Playback;
tileShape?: TileShape;
}
interface IState {
@ -50,15 +53,22 @@ export default class RecordingPlayback extends React.PureComponent<IProps, IStat
this.props.playback.prepare();
}
private get isWaveformable(): boolean {
return this.props.tileShape !== TileShape.Notif
&& this.props.tileShape !== TileShape.FileGrid
&& this.props.tileShape !== TileShape.Pinned;
}
private onPlaybackUpdate = (ev: PlaybackState) => {
this.setState({ playbackPhase: ev });
};
public render(): ReactNode {
return <div className='mx_MediaBody mx_VoiceMessagePrimaryContainer'>
const shapeClass = !this.isWaveformable ? 'mx_VoiceMessagePrimaryContainer_noWaveform' : '';
return <div className={'mx_MediaBody mx_VoiceMessagePrimaryContainer ' + shapeClass}>
<PlayPauseButton playback={this.props.playback} playbackPhase={this.state.playbackPhase} />
<PlaybackClock playback={this.props.playback} />
<PlaybackWaveform playback={this.props.playback} />
{ this.isWaveformable && <PlaybackWaveform playback={this.props.playback} /> }
</div>;
}
}

View file

@ -47,7 +47,7 @@ export default class Waveform extends React.PureComponent<IProps, IState> {
public render() {
return <div className='mx_Waveform'>
{this.props.relHeights.map((h, i) => {
{ this.props.relHeights.map((h, i) => {
const progress = this.props.progress;
const isCompleteBar = (i / this.props.relHeights.length) <= progress && progress > 0;
const classes = classNames({
@ -57,7 +57,7 @@ export default class Waveform extends React.PureComponent<IProps, IState> {
return <span key={i} style={{
"--barHeight": h,
} as WaveformCSSProperties} className={classes} />;
})}
}) }
</div>;
}
}

View file

@ -19,7 +19,7 @@ import { replaceableComponent } from "../../../utils/replaceableComponent";
@replaceableComponent("views.auth.AuthBody")
export default class AuthBody extends React.PureComponent {
render() {
public render(): React.ReactNode {
return <div className="mx_AuthBody">
{ this.props.children }
</div>;

View file

@ -22,7 +22,7 @@ import { replaceableComponent } from "../../../utils/replaceableComponent";
@replaceableComponent("views.auth.AuthFooter")
export default class AuthFooter extends React.Component {
render() {
public render(): React.ReactNode {
return (
<div className="mx_AuthFooter">
<a href="https://matrix.org" target="_blank" rel="noreferrer noopener">{ _t("powered by Matrix") }</a>

View file

@ -16,20 +16,17 @@ limitations under the License.
*/
import React from 'react';
import PropTypes from 'prop-types';
import * as sdk from '../../../index';
import { replaceableComponent } from "../../../utils/replaceableComponent";
import AuthHeaderLogo from "./AuthHeaderLogo";
import LanguageSelector from "./LanguageSelector";
interface IProps {
disableLanguageSelector?: boolean;
}
@replaceableComponent("views.auth.AuthHeader")
export default class AuthHeader extends React.Component {
static propTypes = {
disableLanguageSelector: PropTypes.bool,
};
render() {
const AuthHeaderLogo = sdk.getComponent('auth.AuthHeaderLogo');
const LanguageSelector = sdk.getComponent('views.auth.LanguageSelector');
export default class AuthHeader extends React.Component<IProps> {
public render(): React.ReactNode {
return (
<div className="mx_AuthHeader">
<AuthHeaderLogo />

View file

@ -19,7 +19,7 @@ import { replaceableComponent } from "../../../utils/replaceableComponent";
@replaceableComponent("views.auth.AuthHeaderLogo")
export default class AuthHeaderLogo extends React.PureComponent {
render() {
public render(): React.ReactNode {
return <div className="mx_AuthHeaderLogo">
Matrix
</div>;

View file

@ -17,18 +17,16 @@ limitations under the License.
*/
import React from 'react';
import * as sdk from '../../../index';
import { replaceableComponent } from "../../../utils/replaceableComponent";
import AuthFooter from "./AuthFooter";
@replaceableComponent("views.auth.AuthPage")
export default class AuthPage extends React.PureComponent {
render() {
const AuthFooter = sdk.getComponent('auth.AuthFooter');
public render(): React.ReactNode {
return (
<div className="mx_AuthPage">
<div className="mx_AuthPage_modal">
{this.props.children}
{ this.props.children }
</div>
<AuthFooter />
</div>

View file

@ -15,66 +15,74 @@ limitations under the License.
*/
import React, { createRef } from 'react';
import PropTypes from 'prop-types';
import { _t } from '../../../languageHandler';
import CountlyAnalytics from "../../../CountlyAnalytics";
import { replaceableComponent } from "../../../utils/replaceableComponent";
const DIV_ID = 'mx_recaptcha';
interface ICaptchaFormProps {
sitePublicKey: string;
onCaptchaResponse: (response: string) => void;
}
interface ICaptchaFormState {
errorText?: string;
}
/**
* A pure UI component which displays a captcha form.
*/
@replaceableComponent("views.auth.CaptchaForm")
export default class CaptchaForm extends React.Component {
static propTypes = {
sitePublicKey: PropTypes.string,
// called with the captcha response
onCaptchaResponse: PropTypes.func,
};
export default class CaptchaForm extends React.Component<ICaptchaFormProps, ICaptchaFormState> {
static defaultProps = {
onCaptchaResponse: () => {},
};
constructor(props) {
private captchaWidgetId?: string;
private recaptchaContainer = createRef<HTMLDivElement>();
constructor(props: ICaptchaFormProps) {
super(props);
this.state = {
errorText: null,
errorText: undefined,
};
this._captchaWidgetId = null;
this._recaptchaContainer = createRef();
CountlyAnalytics.instance.track("onboarding_grecaptcha_begin");
}
componentDidMount() {
// Just putting a script tag into the returned jsx doesn't work, annoyingly,
// so we do this instead.
if (global.grecaptcha) {
if (this.isRecaptchaReady()) {
// already loaded
this._onCaptchaLoaded();
this.onCaptchaLoaded();
} else {
console.log("Loading recaptcha script...");
window.mx_on_recaptcha_loaded = () => {this._onCaptchaLoaded();};
window.mxOnRecaptchaLoaded = () => { this.onCaptchaLoaded(); };
const scriptTag = document.createElement('script');
scriptTag.setAttribute(
'src', `https://www.recaptcha.net/recaptcha/api.js?onload=mx_on_recaptcha_loaded&render=explicit`,
'src', `https://www.recaptcha.net/recaptcha/api.js?onload=mxOnRecaptchaLoaded&render=explicit`,
);
this._recaptchaContainer.current.appendChild(scriptTag);
this.recaptchaContainer.current.appendChild(scriptTag);
}
}
componentWillUnmount() {
this._resetRecaptcha();
this.resetRecaptcha();
}
_renderRecaptcha(divId) {
if (!global.grecaptcha) {
// Borrowed directly from: https://github.com/codeep/react-recaptcha-google/commit/e118fa5670fa268426969323b2e7fe77698376ba
private isRecaptchaReady(): boolean {
return typeof window !== "undefined" &&
typeof global.grecaptcha !== "undefined" &&
typeof global.grecaptcha.render === 'function';
}
private renderRecaptcha(divId: string) {
if (!this.isRecaptchaReady()) {
console.error("grecaptcha not loaded!");
throw new Error("Recaptcha did not load successfully");
}
@ -84,26 +92,26 @@ export default class CaptchaForm extends React.Component {
console.error("No public key for recaptcha!");
throw new Error(
"This server has not supplied enough information for Recaptcha "
+ "authentication");
+ "authentication");
}
console.info("Rendering to %s", divId);
this._captchaWidgetId = global.grecaptcha.render(divId, {
this.captchaWidgetId = global.grecaptcha.render(divId, {
sitekey: publicKey,
callback: this.props.onCaptchaResponse,
});
}
_resetRecaptcha() {
if (this._captchaWidgetId !== null) {
global.grecaptcha.reset(this._captchaWidgetId);
private resetRecaptcha() {
if (this.captchaWidgetId !== null) {
global.grecaptcha.reset(this.captchaWidgetId);
}
}
_onCaptchaLoaded() {
private onCaptchaLoaded() {
console.log("Loaded recaptcha script.");
try {
this._renderRecaptcha(DIV_ID);
this.renderRecaptcha(DIV_ID);
// clear error if re-rendered
this.setState({
errorText: null,
@ -128,10 +136,10 @@ export default class CaptchaForm extends React.Component {
}
return (
<div ref={this._recaptchaContainer}>
<p>{_t(
<div ref={this.recaptchaContainer}>
<p>{ _t(
"This homeserver would like to make sure you are not a robot.",
)}</p>
) }</p>
<div id={DIV_ID} />
{ error }
</div>

View file

@ -19,7 +19,7 @@ import { replaceableComponent } from "../../../utils/replaceableComponent";
@replaceableComponent("views.auth.CompleteSecurityBody")
export default class CompleteSecurityBody extends React.PureComponent {
render() {
public render(): React.ReactNode {
return <div className="mx_CompleteSecurityBody">
{ this.props.children }
</div>;

View file

@ -15,21 +15,19 @@ limitations under the License.
*/
import React from 'react';
import PropTypes from 'prop-types';
import * as sdk from '../../../index';
import { COUNTRIES, getEmojiFlag } from '../../../phonenumber';
import { COUNTRIES, getEmojiFlag, PhoneNumberCountryDefinition } from '../../../phonenumber';
import SdkConfig from "../../../SdkConfig";
import { _t } from "../../../languageHandler";
import { replaceableComponent } from "../../../utils/replaceableComponent";
import Dropdown from "../elements/Dropdown";
const COUNTRIES_BY_ISO2 = {};
for (const c of COUNTRIES) {
COUNTRIES_BY_ISO2[c.iso2] = c;
}
function countryMatchesSearchQuery(query, country) {
function countryMatchesSearchQuery(query: string, country: PhoneNumberCountryDefinition): boolean {
// Remove '+' if present (when searching for a prefix)
if (query[0] === '+') {
query = query.slice(1);
@ -41,15 +39,26 @@ function countryMatchesSearchQuery(query, country) {
return false;
}
@replaceableComponent("views.auth.CountryDropdown")
export default class CountryDropdown extends React.Component {
constructor(props) {
super(props);
this._onSearchChange = this._onSearchChange.bind(this);
this._onOptionChange = this._onOptionChange.bind(this);
this._getShortOption = this._getShortOption.bind(this);
interface IProps {
value?: string;
onOptionChange: (country: PhoneNumberCountryDefinition) => void;
isSmall: boolean; // if isSmall, show +44 in the selected value
showPrefix: boolean;
className?: string;
disabled?: boolean;
}
let defaultCountry = COUNTRIES[0];
interface IState {
searchQuery: string;
defaultCountry: PhoneNumberCountryDefinition;
}
@replaceableComponent("views.auth.CountryDropdown")
export default class CountryDropdown extends React.Component<IProps, IState> {
constructor(props: IProps) {
super(props);
let defaultCountry: PhoneNumberCountryDefinition = COUNTRIES[0];
const defaultCountryCode = SdkConfig.get()["defaultCountryCode"];
if (defaultCountryCode) {
const country = COUNTRIES.find(c => c.iso2 === defaultCountryCode.toUpperCase());
@ -62,7 +71,7 @@ export default class CountryDropdown extends React.Component {
};
}
componentDidMount() {
public componentDidMount(): void {
if (!this.props.value) {
// If no value is given, we start with the default
// country selected, but our parent component
@ -71,21 +80,21 @@ export default class CountryDropdown extends React.Component {
}
}
_onSearchChange(search) {
private onSearchChange = (search: string): void => {
this.setState({
searchQuery: search,
});
}
};
_onOptionChange(iso2) {
private onOptionChange = (iso2: string): void => {
this.props.onOptionChange(COUNTRIES_BY_ISO2[iso2]);
}
};
_flagImgForIso2(iso2) {
private flagImgForIso2(iso2: string): React.ReactNode {
return <div className="mx_Dropdown_option_emoji">{ getEmojiFlag(iso2) }</div>;
}
_getShortOption(iso2) {
private getShortOption = (iso2: string): React.ReactNode => {
if (!this.props.isSmall) {
return undefined;
}
@ -94,14 +103,12 @@ export default class CountryDropdown extends React.Component {
countryPrefix = '+' + COUNTRIES_BY_ISO2[iso2].prefix;
}
return <span className="mx_CountryDropdown_shortOption">
{ this._flagImgForIso2(iso2) }
{ this.flagImgForIso2(iso2) }
{ countryPrefix }
</span>;
}
render() {
const Dropdown = sdk.getComponent('elements.Dropdown');
};
public render(): React.ReactNode {
let displayedCountries;
if (this.state.searchQuery) {
displayedCountries = COUNTRIES.filter(
@ -124,7 +131,7 @@ export default class CountryDropdown extends React.Component {
const options = displayedCountries.map((country) => {
return <div className="mx_CountryDropdown_option" key={country.iso2}>
{ this._flagImgForIso2(country.iso2) }
{ this.flagImgForIso2(country.iso2) }
{ _t(country.name) } (+{ country.prefix })
</div>;
});
@ -136,10 +143,10 @@ export default class CountryDropdown extends React.Component {
return <Dropdown
id="mx_CountryDropdown"
className={this.props.className + " mx_CountryDropdown"}
onOptionChange={this._onOptionChange}
onSearchChange={this._onSearchChange}
onOptionChange={this.onOptionChange}
onSearchChange={this.onSearchChange}
menuWidth={298}
getShortOption={this._getShortOption}
getShortOption={this.getShortOption}
value={value}
searchEnabled={true}
disabled={this.props.disabled}
@ -149,13 +156,3 @@ export default class CountryDropdown extends React.Component {
</Dropdown>;
}
}
CountryDropdown.propTypes = {
className: PropTypes.string,
isSmall: PropTypes.bool,
// if isSmall, show +44 in the selected value
showPrefix: PropTypes.bool,
onOptionChange: PropTypes.func.isRequired,
value: PropTypes.string,
disabled: PropTypes.bool,
};

View file

@ -41,7 +41,7 @@ import CaptchaForm from "./CaptchaForm";
* one HS whilst beign a guest on another).
* loginType: the login type of the auth stage being attempted
* authSessionId: session id from the server
* clientSecret: The client secret in use for ID server auth sessions
* clientSecret: The client secret in use for identity server auth sessions
* stageParams: params from the server for the stage being attempted
* errorText: error message from a previous attempt to authenticate
* submitAuthDict: a function which will be called with the new auth dict
@ -54,8 +54,8 @@ import CaptchaForm from "./CaptchaForm";
* Defined keys for stages are:
* m.login.email.identity:
* * emailSid: string representing the sid of the active
* verification session from the ID server, or
* null if no session is active.
* verification session from the identity server,
* or null if no session is active.
* fail: a function which should be called with an error object if an
* error occurred during the auth stage. This will cause the auth
* session to be failed and the process to go back to the start.
@ -417,12 +417,12 @@ export class TermsAuthEntry extends React.Component<ITermsAuthEntryProps, ITerms
if (this.props.showContinue !== false) {
// XXX: button classes
submitButton = <button className="mx_InteractiveAuthEntryComponents_termsSubmit mx_GeneralButton"
onClick={this.trySubmit} disabled={!allChecked}>{_t("Accept")}</button>;
onClick={this.trySubmit} disabled={!allChecked}>{ _t("Accept") }</button>;
}
return (
<div>
<p>{_t("Please review and accept the policies of this homeserver:")}</p>
<p>{ _t("Please review and accept the policies of this homeserver:") }</p>
{ checkboxes }
{ errorSection }
{ submitButton }
@ -613,7 +613,7 @@ export class MsisdnAuthEntry extends React.Component<IMsisdnAuthEntryProps, IMsi
className="mx_InteractiveAuthEntryComponents_msisdnEntry"
value={this.state.token}
onChange={this.onTokenChange}
aria-label={ _t("Code")}
aria-label={_t("Code")}
/>
<br />
<input type="submit" value={_t("Submit")}
@ -621,7 +621,7 @@ export class MsisdnAuthEntry extends React.Component<IMsisdnAuthEntryProps, IMsi
disabled={!enableSubmit}
/>
</form>
{errorSection}
{ errorSection }
</div>
</div>
);
@ -717,21 +717,21 @@ export class SSOAuthEntry extends React.Component<ISSOAuthEntryProps, ISSOAuthEn
<AccessibleButton
onClick={this.props.onCancel}
kind={this.props.continueKind ? (this.props.continueKind + '_outline') : 'primary_outline'}
>{_t("Cancel")}</AccessibleButton>
>{ _t("Cancel") }</AccessibleButton>
);
if (this.state.phase === SSOAuthEntry.PHASE_PREAUTH) {
continueButton = (
<AccessibleButton
onClick={this.onStartAuthClick}
kind={this.props.continueKind || 'primary'}
>{this.props.continueText || _t("Single Sign On")}</AccessibleButton>
>{ this.props.continueText || _t("Single Sign On") }</AccessibleButton>
);
} else {
continueButton = (
<AccessibleButton
onClick={this.onConfirmClick}
kind={this.props.continueKind || 'primary'}
>{this.props.continueText || _t("Confirm")}</AccessibleButton>
>{ this.props.continueText || _t("Confirm") }</AccessibleButton>
);
}
@ -753,8 +753,8 @@ export class SSOAuthEntry extends React.Component<ISSOAuthEntryProps, ISSOAuthEn
return <React.Fragment>
{ errorSection }
<div className="mx_InteractiveAuthEntryComponents_sso_buttons">
{cancelButton}
{continueButton}
{ cancelButton }
{ continueButton }
</div>
</React.Fragment>;
}
@ -825,7 +825,7 @@ export class FallbackAuthEntry extends React.Component<IAuthEntryProps> {
<a href="" ref={this.fallbackButton} onClick={this.onShowFallbackClick}>{
_t("Start authentication")
}</a>
{errorSection}
{ errorSection }
</div>
);
}

View file

@ -18,21 +18,23 @@ import SdkConfig from "../../../SdkConfig";
import { getCurrentLanguage } from "../../../languageHandler";
import SettingsStore from "../../../settings/SettingsStore";
import PlatformPeg from "../../../PlatformPeg";
import * as sdk from '../../../index';
import React from 'react';
import { SettingLevel } from "../../../settings/SettingLevel";
import LanguageDropdown from "../elements/LanguageDropdown";
function onChange(newLang) {
function onChange(newLang: string): void {
if (getCurrentLanguage() !== newLang) {
SettingsStore.setValue("language", null, SettingLevel.DEVICE, newLang);
PlatformPeg.get().reload();
}
}
export default function LanguageSelector({ disabled }) {
if (SdkConfig.get()['disable_login_language_selector']) return <div />;
interface IProps {
disabled?: boolean;
}
const LanguageDropdown = sdk.getComponent('views.elements.LanguageDropdown');
export default function LanguageSelector({ disabled }: IProps): JSX.Element {
if (SdkConfig.get()['disable_login_language_selector']) return <div />;
return <LanguageDropdown
className="mx_AuthBody_language"
onOptionChange={onChange}

View file

@ -416,7 +416,7 @@ export default class PasswordLogin extends React.PureComponent<IProps, IState> {
kind="link"
onClick={this.onForgotPasswordClick}
>
{_t("Forgot password?")}
{ _t("Forgot password?") }
</AccessibleButton>;
}
@ -441,16 +441,16 @@ export default class PasswordLogin extends React.PureComponent<IProps, IState> {
disabled={this.props.disableSubmit}
>
<option key={LoginField.MatrixId} value={LoginField.MatrixId}>
{_t('Username')}
{ _t('Username') }
</option>
<option
key={LoginField.Email}
value={LoginField.Email}
>
{_t('Email address')}
{ _t('Email address') }
</option>
<option key={LoginField.Password} value={LoginField.Password}>
{_t('Phone')}
{ _t('Phone') }
</option>
</Field>
</div>
@ -460,8 +460,8 @@ export default class PasswordLogin extends React.PureComponent<IProps, IState> {
return (
<div>
<form onSubmit={this.onSubmitForm}>
{loginType}
{loginField}
{ loginType }
{ loginField }
<Field
className={pwFieldClass}
type="password"
@ -474,7 +474,7 @@ export default class PasswordLogin extends React.PureComponent<IProps, IState> {
onValidate={this.onPasswordValidate}
ref={field => this[LoginField.Password] = field}
/>
{forgotPasswordJsx}
{ forgotPasswordJsx }
{ !this.props.busy && <input className="mx_Login_submit"
type="submit"
value={_t('Sign in')}

View file

@ -537,15 +537,15 @@ export default class RegistrationForm extends React.PureComponent<IProps, IState
<div>
<form onSubmit={this.onSubmit}>
<div className="mx_AuthBody_fieldRow">
{this.renderUsername()}
{ this.renderUsername() }
</div>
<div className="mx_AuthBody_fieldRow">
{this.renderPassword()}
{this.renderPasswordConfirm()}
{ this.renderPassword() }
{ this.renderPasswordConfirm() }
</div>
<div className="mx_AuthBody_fieldRow">
{this.renderEmail()}
{this.renderPhoneNumber()}
{ this.renderEmail() }
{ this.renderPhoneNumber() }
</div>
{ emailHelperText }
{ registerButton }

View file

@ -17,7 +17,7 @@ limitations under the License.
import React from 'react';
import classNames from "classnames";
import * as sdk from '../../../index';
import * as sdk from "../../../index";
import SdkConfig from '../../../SdkConfig';
import AuthPage from "./AuthPage";
import { _td } from "../../../languageHandler";
@ -25,21 +25,26 @@ import SettingsStore from "../../../settings/SettingsStore";
import { UIFeature } from "../../../settings/UIFeature";
import CountlyAnalytics from "../../../CountlyAnalytics";
import { replaceableComponent } from "../../../utils/replaceableComponent";
import LanguageSelector from "./LanguageSelector";
// translatable strings for Welcome pages
_td("Sign in with SSO");
interface IProps {
}
@replaceableComponent("views.auth.Welcome")
export default class Welcome extends React.PureComponent {
constructor(props) {
export default class Welcome extends React.PureComponent<IProps> {
constructor(props: IProps) {
super(props);
CountlyAnalytics.instance.track("onboarding_welcome");
}
render() {
const EmbeddedPage = sdk.getComponent('structures.EmbeddedPage');
const LanguageSelector = sdk.getComponent('auth.LanguageSelector');
public render(): React.ReactNode {
// FIXME: Using an import will result in wrench-element-tests failures
const EmbeddedPage = sdk.getComponent("structures.EmbeddedPage");
const pagesConfig = SdkConfig.get().embeddedPages;
let pageUrl = null;

View file

@ -205,8 +205,8 @@ export default class DecoratedRoomAvatar extends React.PureComponent<IProps, ISt
oobData={this.props.oobData}
viewAvatarOnClick={this.props.viewAvatarOnClick}
/>
{icon}
{badge}
{ icon }
{ badge }
</div>;
}
}

View file

@ -145,7 +145,7 @@ export default class MemberStatusMessageAvatar extends React.Component {
isExpanded={this.state.menuDisplayed}
label={_t("User Status")}
>
{avatar}
{ avatar }
</ContextMenuButton>
{ contextMenu }

View file

@ -22,6 +22,7 @@ import ImageView from '../elements/ImageView';
import { MatrixClientPeg } from '../../../MatrixClientPeg';
import Modal from '../../../Modal';
import * as Avatar from '../../../Avatar';
import DMRoomMap from "../../../utils/DMRoomMap";
import { replaceableComponent } from "../../../utils/replaceableComponent";
import { mediaFromMxc } from "../../../customisations/Media";
import { IOOBData } from '../../../stores/ThreepidInviteStore';
@ -131,11 +132,14 @@ export default class RoomAvatar extends React.Component<IProps, IState> {
const { room, oobData, viewAvatarOnClick, onClick, ...otherProps } = this.props;
const roomName = room ? room.name : oobData.name;
// If the room is a DM, we use the other user's ID for the color hash
// in order to match the room avatar with their avatar
const idName = room ? (DMRoomMap.shared().getUserIdForRoomId(room.roomId) ?? room.roomId) : null;
return (
<BaseAvatar {...otherProps}
name={roomName}
idName={room ? room.roomId : null}
idName={idName}
urls={this.state.urls}
onClick={viewAvatarOnClick && this.state.urls[0] ? this.onRoomAvatarClick : onClick}
/>

View file

@ -105,7 +105,7 @@ const BetaCard = ({ title: titleOverride, featureId }: IProps) => {
</div>
<img src={image} alt="" />
</div>
{ extraSettings && <div className="mx_BetaCard_relatedSettings">
{ extraSettings && value && <div className="mx_BetaCard_relatedSettings">
{ extraSettings.map(key => (
<SettingsFlag key={key} name={key} level={SettingLevel.DEVICE} />
)) }

View file

@ -53,7 +53,7 @@ export default class CallContextMenu extends React.Component<IProps> {
onTransferClick = () => {
Modal.createTrackedDialog(
'Transfer Call', '', InviteDialog, { kind: KIND_CALL_TRANSFER, call: this.props.call },
/*className=*/null, /*isPriority=*/false, /*isStatic=*/true,
/*className=*/"mx_InviteDialog_transferWrapper", /*isPriority=*/false, /*isStatic=*/true,
);
this.props.onFinished();
};
@ -65,15 +65,15 @@ export default class CallContextMenu extends React.Component<IProps> {
let transferItem;
if (this.props.call.opponentCanBeTransferred()) {
transferItem = <MenuItem className="mx_CallContextMenu_item" onClick={this.onTransferClick}>
{_t("Transfer")}
{ _t("Transfer") }
</MenuItem>;
}
return <ContextMenu {...this.props}>
<MenuItem className="mx_CallContextMenu_item" onClick={handler}>
{holdUnholdCaption}
{ holdUnholdCaption }
</MenuItem>
{transferItem}
{ transferItem }
</ContextMenu>;
}
}

View file

@ -15,11 +15,11 @@ limitations under the License.
*/
import React from 'react';
import { _t } from '../../../languageHandler';
import AccessibleButton from "../elements/AccessibleButton";
import { ContextMenu, IProps as IContextMenuProps } from '../../structures/ContextMenu';
import { MatrixCall } from 'matrix-js-sdk/src/webrtc/call';
import Field from "../elements/Field";
import Dialpad from '../voip/DialPad';
import DialPad from '../voip/DialPad';
import { replaceableComponent } from "../../../utils/replaceableComponent";
interface IProps extends IContextMenuProps {
@ -45,24 +45,29 @@ export default class DialpadContextMenu extends React.Component<IProps, IState>
this.setState({ value: this.state.value + digit });
};
onCancelClick = () => {
this.props.onFinished();
};
onChange = (ev) => {
this.setState({ value: ev.target.value });
};
render() {
return <ContextMenu {...this.props}>
<div className="mx_DialPadContextMenu_header">
<div className="mx_DialPadContextMenuWrapper">
<div>
<span className="mx_DialPadContextMenu_title">{_t("Dial pad")}</span>
<AccessibleButton className="mx_DialPadContextMenu_cancel" onClick={this.onCancelClick} />
</div>
<div className="mx_DialPadContextMenu_header">
<Field className="mx_DialPadContextMenu_dialled"
value={this.state.value} autoFocus={true}
onChange={this.onChange}
/>
</div>
<div className="mx_DialPadContextMenu_dialPad">
<DialPad onDigitPress={this.onDigitPress} hasDial={false} />
</div>
<Field className="mx_DialPadContextMenu_dialled"
value={this.state.value} autoFocus={true}
onChange={this.onChange}
/>
</div>
<div className="mx_DialPadContextMenu_horizSep" />
<div className="mx_DialPadContextMenu_dialPad">
<Dialpad onDigitPress={this.onDigitPress} hasDialAndDelete={false} />
</div>
</ContextMenu>;
}

View file

@ -64,8 +64,8 @@ export const IconizedContextMenuRadio: React.FC<IRadioProps> = ({
label={label}
>
<span className={classNames("mx_IconizedContextMenu_icon", iconClassName)} />
<span className="mx_IconizedContextMenu_label">{label}</span>
{active && <span className="mx_IconizedContextMenu_icon mx_IconizedContextMenu_checked" />}
<span className="mx_IconizedContextMenu_label">{ label }</span>
{ active && <span className="mx_IconizedContextMenu_icon mx_IconizedContextMenu_checked" /> }
</MenuItemRadio>;
};
@ -85,15 +85,15 @@ export const IconizedContextMenuCheckbox: React.FC<ICheckboxProps> = ({
label={label}
>
<span className={classNames("mx_IconizedContextMenu_icon", iconClassName)} />
<span className="mx_IconizedContextMenu_label">{label}</span>
{active && <span className="mx_IconizedContextMenu_icon mx_IconizedContextMenu_checked" />}
<span className="mx_IconizedContextMenu_label">{ label }</span>
{ active && <span className="mx_IconizedContextMenu_icon mx_IconizedContextMenu_checked" /> }
</MenuItemCheckbox>;
};
export const IconizedContextMenuOption: React.FC<IOptionProps> = ({ label, iconClassName, ...props }) => {
return <MenuItem {...props} label={label}>
{ iconClassName && <span className={classNames("mx_IconizedContextMenu_icon", iconClassName)} /> }
<span className="mx_IconizedContextMenu_label">{label}</span>
<span className="mx_IconizedContextMenu_label">{ label }</span>
</MenuItem>;
};
@ -104,7 +104,7 @@ export const IconizedContextMenuOptionList: React.FC<IOptionListProps> = ({ firs
});
return <div className={classes}>
{children}
{ children }
</div>;
};

View file

@ -43,11 +43,15 @@ export function canCancel(eventStatus: EventStatus): boolean {
return eventStatus === EventStatus.QUEUED || eventStatus === EventStatus.NOT_SENT;
}
interface IEventTileOps {
export interface IEventTileOps {
isWidgetHidden(): boolean;
unhideWidget(): void;
}
export interface IOperableEventTile {
getEventTileOps(): IEventTileOps;
}
interface IProps {
/* the MatrixEvent associated with the context menu */
mxEvent: MatrixEvent;
@ -268,7 +272,7 @@ export default class MessageContextMenu extends React.Component<IProps, IState>
resendReactionsButton = (
<IconizedContextMenuOption
iconClassName="mx_MessageContextMenu_iconResend"
label={ _t('Resend %(unsentCount)s reaction(s)', { unsentCount: unsentReactionsCount }) }
label={_t('Resend %(unsentCount)s reaction(s)', { unsentCount: unsentReactionsCount })}
onClick={this.onResendReactionsClick}
/>
);
@ -298,7 +302,7 @@ export default class MessageContextMenu extends React.Component<IProps, IState>
pinButton = (
<IconizedContextMenuOption
iconClassName="mx_MessageContextMenu_iconPin"
label={ this.isPinned() ? _t('Unpin') : _t('Pin') }
label={this.isPinned() ? _t('Unpin') : _t('Pin')}
onClick={this.onPinClick}
/>
);
@ -333,7 +337,7 @@ export default class MessageContextMenu extends React.Component<IProps, IState>
<IconizedContextMenuOption
iconClassName="mx_MessageContextMenu_iconPermalink"
onClick={this.onPermalinkClick}
label= {_t('Share')}
label={_t('Share')}
element="a"
{
// XXX: Typescript signature for AccessibleButton doesn't work properly for non-inputs like `a`
@ -364,7 +368,7 @@ export default class MessageContextMenu extends React.Component<IProps, IState>
<IconizedContextMenuOption
iconClassName="mx_MessageContextMenu_iconLink"
onClick={this.closeMenu}
label={ _t('Source URL') }
label={_t('Source URL')}
element="a"
{
// XXX: Typescript signature for AccessibleButton doesn't work properly for non-inputs like `a`

View file

@ -99,20 +99,20 @@ export default class StatusMessageContextMenu extends React.Component {
actionButton = <AccessibleButton className="mx_StatusMessageContextMenu_clear"
onClick={this._onClearClick}
>
<span>{_t("Clear status")}</span>
<span>{ _t("Clear status") }</span>
</AccessibleButton>;
} else {
actionButton = <AccessibleButton className="mx_StatusMessageContextMenu_submit"
onClick={this._onSubmit}
>
<span>{_t("Update status")}</span>
<span>{ _t("Update status") }</span>
</AccessibleButton>;
}
} else {
actionButton = <AccessibleButton className="mx_StatusMessageContextMenu_submit"
disabled={!this.state.message} onClick={this._onSubmit}
>
<span>{_t("Set status")}</span>
<span>{ _t("Set status") }</span>
</AccessibleButton>;
}
@ -130,8 +130,8 @@ export default class StatusMessageContextMenu extends React.Component {
onChange={this._onStatusChange}
/>
<div className="mx_StatusMessageContextMenu_actionContainer">
{actionButton}
{spinner}
{ actionButton }
{ spinner }
</div>
</form>;

View file

@ -221,7 +221,7 @@ export const AddExistingToSpace: React.FC<IAddExistingToSpaceProps> = ({
return <div className="mx_AddExistingToSpace">
<SearchBox
className="mx_textinput_icon mx_textinput_search"
placeholder={ _t("Filter your rooms and spaces") }
placeholder={_t("Filter your rooms and spaces")}
onSearch={setQuery}
autoComplete={true}
autoFocus={true}

View file

@ -620,7 +620,7 @@ export default class AddressPickerDialog extends React.Component {
let inputLabel;
if (this.props.description) {
inputLabel = <div className="mx_AddressPickerDialog_label">
<label htmlFor="textinput">{this.props.description}</label>
<label htmlFor="textinput">{ this.props.description }</label>
</div>;
}
@ -690,7 +690,7 @@ export default class AddressPickerDialog extends React.Component {
&& this.props.validAddressTypes.includes('email')) {
const defaultIdentityServerUrl = getDefaultIdentityServerUrl();
if (defaultIdentityServerUrl) {
identityServer = <div className="mx_AddressPickerDialog_identityServer">{_t(
identityServer = <div className="mx_AddressPickerDialog_identityServer">{ _t(
"Use an identity server to invite by email. " +
"<default>Use the default (%(defaultIdentityServerName)s)</default> " +
"or manage in <settings>Settings</settings>.",
@ -698,25 +698,25 @@ export default class AddressPickerDialog extends React.Component {
defaultIdentityServerName: abbreviateUrl(defaultIdentityServerUrl),
},
{
default: sub => <a href="#" onClick={this.onUseDefaultIdentityServerClick}>{sub}</a>,
settings: sub => <a href="#" onClick={this.onManageSettingsClick}>{sub}</a>,
default: sub => <a href="#" onClick={this.onUseDefaultIdentityServerClick}>{ sub }</a>,
settings: sub => <a href="#" onClick={this.onManageSettingsClick}>{ sub }</a>,
},
)}</div>;
) }</div>;
} else {
identityServer = <div className="mx_AddressPickerDialog_identityServer">{_t(
identityServer = <div className="mx_AddressPickerDialog_identityServer">{ _t(
"Use an identity server to invite by email. " +
"Manage in <settings>Settings</settings>.",
{}, {
settings: sub => <a href="#" onClick={this.onManageSettingsClick}>{sub}</a>,
settings: sub => <a href="#" onClick={this.onManageSettingsClick}>{ sub }</a>,
},
)}</div>;
) }</div>;
}
}
return (
<BaseDialog className="mx_AddressPickerDialog" onKeyDown={this.onKeyDown}
onFinished={this.props.onFinished} title={this.props.title}>
{inputLabel}
{ inputLabel }
<div className="mx_Dialog_content">
<div className="mx_AddressPickerDialog_inputContainer">{ query }</div>
{ error }

View file

@ -51,7 +51,7 @@ export default class AskInviteAnywayDialog extends React.Component<IProps> {
public render() {
const errorList = this.props.unknownProfileUsers
.map(address => <li key={address.userId}>{address.userId}: {address.errorText}</li>);
.map(address => <li key={address.userId}>{ address.userId }: { address.errorText }</li>);
return (
<BaseDialog className='mx_RetryInvitesDialog'
@ -60,8 +60,8 @@ export default class AskInviteAnywayDialog extends React.Component<IProps> {
contentId='mx_Dialog_content'
>
<div id='mx_Dialog_content'>
{/* eslint-disable-next-line */}
<p>{_t("Unable to find profiles for the Matrix IDs listed below - would you like to invite them anyway?")}</p>
<p>{ _t("Unable to find profiles for the Matrix IDs listed below - " +
"would you like to invite them anyway?") }</p>
<ul>
{ errorList }
</ul>

View file

@ -149,7 +149,7 @@ export default class BaseDialog extends React.Component {
'mx_Dialog_headerWithCancel': !!cancelButton,
})}>
<div className={classNames('mx_Dialog_title', this.props.titleClass)} id='mx_BaseDialog_title'>
{headerImage}
{ headerImage }
{ this.props.title }
</div>
{ this.props.headerButton }

View file

@ -69,7 +69,7 @@ const BetaFeedbackDialog: React.FC<IProps> = ({ featureId, onFinished }) => {
<div className="mx_BetaFeedbackDialog_subheading">
{ _t(info.feedbackSubheading) }
&nbsp;
{ _t("Your platform and username will be noted to help us use your feedback as much as we can.")}
{ _t("Your platform and username will be noted to help us use your feedback as much as we can.") }
<AccessibleButton kind="link" onClick={() => {
onFinished(false);

View file

@ -166,7 +166,7 @@ export default class BugReportDialog extends React.Component<IProps, IState> {
let error = null;
if (this.state.err) {
error = <div className="error">
{this.state.err}
{ this.state.err }
</div>;
}
@ -175,7 +175,7 @@ export default class BugReportDialog extends React.Component<IProps, IState> {
progress = (
<div className="progress">
<Spinner />
{this.state.progress} ...
{ this.state.progress } ...
</div>
);
}
@ -221,7 +221,7 @@ export default class BugReportDialog extends React.Component<IProps, IState> {
<AccessibleButton onClick={this.onDownload} kind="link" disabled={this.state.downloadBusy}>
{ _t("Download logs") }
</AccessibleButton>
{this.state.downloadProgress && <span>{this.state.downloadProgress} ...</span>}
{ this.state.downloadProgress && <span>{ this.state.downloadProgress } ...</span> }
</div>
<Field
@ -246,8 +246,8 @@ export default class BugReportDialog extends React.Component<IProps, IState> {
"please include those things here.",
)}
/>
{progress}
{error}
{ progress }
{ error }
</div>
<DialogButtons primaryButton={_t("Send logs")}
onPrimaryButtonClick={this.onSubmit}

View file

@ -59,7 +59,7 @@ export default class ChangelogDialog extends React.Component<IProps> {
return (
<li key={commit.sha} className="mx_ChangelogDialog_li">
<a href={commit.html_url} target="_blank" rel="noreferrer noopener">
{commit.commit.message.split('\n')[0]}
{ commit.commit.message.split('\n')[0] }
</a>
</li>
);
@ -79,15 +79,15 @@ export default class ChangelogDialog extends React.Component<IProps> {
}
return (
<div key={repo}>
<h2>{repo}</h2>
<ul>{content}</ul>
<h2>{ repo }</h2>
<ul>{ content }</ul>
</div>
);
});
const content = (
<div className="mx_ChangelogDialog_content">
{this.props.version == null || this.props.newVersion == null ? <h2>{_t("Unavailable")}</h2> : logs}
{ this.props.version == null || this.props.newVersion == null ? <h2>{ _t("Unavailable") }</h2> : logs }
</div>
);

View file

@ -156,8 +156,8 @@ export default class CommunityPrototypeInviteDialog extends React.PureComponent<
height={avatarSize}
/>
<div className="mx_CommunityPrototypeInviteDialog_personIdentifiers">
<span className="mx_CommunityPrototypeInviteDialog_personName">{person.user.name}</span>
<span className="mx_CommunityPrototypeInviteDialog_personId">{person.userId}</span>
<span className="mx_CommunityPrototypeInviteDialog_personName">{ person.user.name }</span>
<span className="mx_CommunityPrototypeInviteDialog_personId">{ person.userId }</span>
</div>
<StyledCheckbox onChange={(e) => this.setPersonToggle(person, e.target.checked)} />
</div>
@ -187,7 +187,7 @@ export default class CommunityPrototypeInviteDialog extends React.PureComponent<
emailAddresses.push((
<Field
key={emailAddresses.length}
value={""}
value=""
onChange={(e) => this.onAddressChange(e, emailAddresses.length)}
label={emailAddresses.length > 0 ? _t("Add another email") : _t("Email address")}
placeholder={emailAddresses.length > 0 ? _t("Add another email") : _t("Email address")}
@ -207,16 +207,16 @@ export default class CommunityPrototypeInviteDialog extends React.PureComponent<
onClick={this.onShowMorePeople}
kind="link" key="more"
className="mx_CommunityPrototypeInviteDialog_morePeople"
>{_t("Show more")}</AccessibleButton>
>{ _t("Show more") }</AccessibleButton>
));
}
}
if (this.state.people.length > 0) {
peopleIntro = (
<div className="mx_CommunityPrototypeInviteDialog_people">
<span>{_t("People you know on %(brand)s", { brand: SdkConfig.get().brand })}</span>
<span>{ _t("People you know on %(brand)s", { brand: SdkConfig.get().brand }) }</span>
<AccessibleButton onClick={this.onShowPeopleClick}>
{this.state.showPeople ? _t("Hide") : _t("Show")}
{ this.state.showPeople ? _t("Hide") : _t("Show") }
</AccessibleButton>
</div>
);
@ -236,14 +236,14 @@ export default class CommunityPrototypeInviteDialog extends React.PureComponent<
>
<form onSubmit={this.onSubmit}>
<div className="mx_Dialog_content">
{emailAddresses}
{peopleIntro}
{people}
{ emailAddresses }
{ peopleIntro }
{ people }
<AccessibleButton
kind="primary" onClick={this.onSubmit}
disabled={this.state.busy}
className="mx_CommunityPrototypeInviteDialog_primaryButton"
>{buttonText}</AccessibleButton>
>{ buttonText }</AccessibleButton>
</div>
</form>
</BaseDialog>

View file

@ -44,10 +44,10 @@ export default class ConfirmWipeDeviceDialog extends React.Component<IProps> {
>
<div className='mx_ConfirmWipeDeviceDialog_content'>
<p>
{_t(
{ _t(
"Clearing all data from this session is permanent. Encrypted messages will be lost " +
"unless their keys have been backed up.",
)}
) }
</p>
</div>
<DialogButtons

View file

@ -144,11 +144,11 @@ export default class CreateCommunityPrototypeDialog extends React.PureComponent<
if (this.state.localpart) {
communityId = (
<span className="mx_CreateCommunityPrototypeDialog_communityId">
{_t("Community ID: +<localpart />:%(domain)s", {
{ _t("Community ID: +<localpart />:%(domain)s", {
domain: MatrixClientPeg.getHomeserverName(),
}, {
localpart: () => <u>{this.state.localpart}</u>,
})}
localpart: () => <u>{ this.state.localpart }</u>,
}) }
<InfoTooltip
tooltip={_t(
"Use this when referencing your community to others. The community ID " +
@ -161,14 +161,14 @@ export default class CreateCommunityPrototypeDialog extends React.PureComponent<
let helpText = (
<span className="mx_CreateCommunityPrototypeDialog_subtext">
{_t("You can change this later if needed.")}
{ _t("You can change this later if needed.") }
</span>
);
if (this.state.error) {
const classes = "mx_CreateCommunityPrototypeDialog_subtext mx_CreateCommunityPrototypeDialog_subtext_error";
helpText = (
<span className={classes}>
{this.state.error}
{ this.state.error }
</span>
);
}
@ -193,13 +193,13 @@ export default class CreateCommunityPrototypeDialog extends React.PureComponent<
placeholder={_t("Enter name")}
label={_t("Enter name")}
/>
{helpText}
{ helpText }
<span className="mx_CreateCommunityPrototypeDialog_subtext">
{/*nbsp is to reserve the height of this element when there's nothing*/}
&nbsp;{communityId}
{ /*nbsp is to reserve the height of this element when there's nothing*/ }
&nbsp;{ communityId }
</span>
<AccessibleButton kind="primary" onClick={this.onSubmit} disabled={this.state.busy}>
{_t("Create")}
{ _t("Create") }
</AccessibleButton>
</div>
<div className="mx_CreateCommunityPrototypeDialog_colAvatar">
@ -212,12 +212,12 @@ export default class CreateCommunityPrototypeDialog extends React.PureComponent<
onClick={this.onChangeAvatar}
className="mx_CreateCommunityPrototypeDialog_avatarContainer"
>
{preview}
{ preview }
</AccessibleButton>
<div className="mx_CreateCommunityPrototypeDialog_tip">
<b>{_t("Add image (optional)")}</b>
<b>{ _t("Add image (optional)") }</b>
<span>
{_t("An image will help people identify your community.")}
{ _t("An image will help people identify your community.") }
</span>
</div>
</div>

View file

@ -102,7 +102,7 @@ export default class CreateGroupDialog extends React.Component<IProps, IState> {
});
};
_onCancel = () => {
private onCancel = () => {
this.props.onFinished(false);
};
@ -167,7 +167,7 @@ export default class CreateGroupDialog extends React.Component<IProps, IState> {
</div>
<div className="mx_Dialog_buttons">
<input type="submit" value={_t('Create')} className="mx_Dialog_primary" />
<button onClick={this._onCancel}>
<button onClick={this.onCancel}>
{ _t("Cancel") }
</button>
</div>

View file

@ -224,15 +224,15 @@ export default class CreateRoomDialog extends React.Component<IProps, IState> {
);
}
let publicPrivateLabel = <p>{_t(
let publicPrivateLabel = <p>{ _t(
"Private rooms can be found and joined by invitation only. Public rooms can be " +
"found and joined by anyone.",
)}</p>;
) }</p>;
if (CommunityPrototypeStore.instance.getSelectedCommunityId()) {
publicPrivateLabel = <p>{_t(
publicPrivateLabel = <p>{ _t(
"Private rooms can be found and joined by invitation only. Public rooms can be " +
"found and joined by anyone in this community.",
)}</p>;
) }</p>;
}
let e2eeSection;
@ -250,7 +250,7 @@ export default class CreateRoomDialog extends React.Component<IProps, IState> {
}
e2eeSection = <React.Fragment>
<LabelledToggleSwitch
label={ _t("Enable end-to-end encryption")}
label={_t("Enable end-to-end encryption")}
onChange={this.onEncryptedChange}
value={this.state.isEncrypted}
className='mx_CreateRoomDialog_e2eSwitch' // for end-to-end tests
@ -318,7 +318,7 @@ export default class CreateRoomDialog extends React.Component<IProps, IState> {
onChange={this.onNoFederateChange}
value={this.state.noFederate}
/>
<p>{federateLabel}</p>
<p>{ federateLabel }</p>
</details>
</div>
</form>

View file

@ -172,11 +172,11 @@ export default class DeactivateAccountDialog extends React.Component<IProps, ISt
</div>;
}
let auth = <div>{_t("Loading...")}</div>;
let auth = <div>{ _t("Loading...") }</div>;
if (this.state.authData && this.state.authEnabled) {
auth = (
<div>
{this.state.bodyText}
{ this.state.bodyText }
<InteractiveAuth
matrixClient={MatrixClientPeg.get()}
authData={this.state.authData}
@ -230,18 +230,18 @@ export default class DeactivateAccountDialog extends React.Component<IProps, ISt
checked={this.state.shouldErase}
onChange={this.onEraseFieldChange}
>
{_t(
{ _t(
"Please forget all messages I have sent when my account is deactivated " +
"(<b>Warning:</b> this will cause future users to see an incomplete view " +
"of conversations)",
{},
{ b: (sub) => <b>{ sub }</b> },
)}
) }
</StyledCheckbox>
</p>
{error}
{auth}
{ error }
{ auth }
</div>
</div>

View file

@ -337,7 +337,7 @@ class FilteredList extends React.PureComponent<IFilteredListProps, IFilteredList
}
// TODO: [REACT-WARNING] Replace with appropriate lifecycle event
UNSAFE_componentWillReceiveProps(nextProps) { // eslint-disable-line camelcase
UNSAFE_componentWillReceiveProps(nextProps) { // eslint-disable-line
if (this.props.children === nextProps.children && this.props.query === nextProps.query) return;
this.setState({
filteredChildren: FilteredList.filterChildren(nextProps.children, nextProps.query),
@ -494,7 +494,7 @@ class RoomStateExplorer extends React.PureComponent<IExplorerProps, IRoomStateEx
}
return <button className={classes} key={eventType} onClick={onClickFn}>
{eventType}
{ eventType }
</button>;
})
}
@ -726,17 +726,17 @@ const VerificationRequestExplorer: React.FC<{
return (<div className="mx_DevTools_VerificationRequest">
<dl>
<dt>Transaction</dt>
<dd>{txnId}</dd>
<dd>{ txnId }</dd>
<dt>Phase</dt>
<dd>{PHASE_MAP[request.phase] || request.phase}</dd>
<dd>{ PHASE_MAP[request.phase] || request.phase }</dd>
<dt>Timeout</dt>
<dd>{Math.floor(timeout / 1000)}</dd>
<dd>{ Math.floor(timeout / 1000) }</dd>
<dt>Methods</dt>
<dd>{request.methods && request.methods.join(", ")}</dd>
<dd>{ request.methods && request.methods.join(", ") }</dd>
<dt>requestingUserId</dt>
<dd>{request.requestingUserId}</dd>
<dd>{ request.requestingUserId }</dd>
<dt>observeOnly</dt>
<dd>{JSON.stringify(request.observeOnly)}</dd>
<dd>{ JSON.stringify(request.observeOnly) }</dd>
</dl>
</div>);
};
@ -771,12 +771,12 @@ class VerificationExplorer extends React.PureComponent<IExplorerProps> {
return (<div>
<div className="mx_Dialog_content">
{Array.from(inRoomRequests.entries()).reverse().map(([txnId, request]) =>
{ Array.from(inRoomRequests.entries()).reverse().map(([txnId, request]) =>
<VerificationRequestExplorer txnId={txnId} request={request} key={txnId} />,
)}
) }
</div>
<div className="mx_Dialog_buttons">
<button onClick={this.props.onBack}>{_t("Back")}</button>
<button onClick={this.props.onBack}>{ _t("Back") }</button>
</div>
</div>);
}
@ -844,9 +844,9 @@ class WidgetExplorer extends React.Component<IExplorerProps, IWidgetExplorerStat
const stateEv = allState.find(ev => ev.getId() === editWidget.eventId);
if (!stateEv) { // "should never happen"
return <div>
{_t("There was an error finding this widget.")}
{ _t("There was an error finding this widget.") }
<div className="mx_Dialog_buttons">
<button onClick={this.onBack}>{_t("Back")}</button>
<button onClick={this.onBack}>{ _t("Back") }</button>
</div>
</div>;
}
@ -865,17 +865,17 @@ class WidgetExplorer extends React.Component<IExplorerProps, IWidgetExplorerStat
return (<div>
<div className="mx_Dialog_content">
<FilteredList query={this.state.query} onChange={this.onQueryChange}>
{widgets.map(w => {
{ widgets.map(w => {
return <button
className='mx_DevTools_RoomStateExplorer_button'
key={w.url + w.eventId}
onClick={() => this.onEditWidget(w)}
>{w.url}</button>;
})}
>{ w.url }</button>;
}) }
</FilteredList>
</div>
<div className="mx_Dialog_buttons">
<button onClick={this.onBack}>{_t("Back")}</button>
<button onClick={this.onBack}>{ _t("Back") }</button>
</div>
</div>);
}
@ -1007,7 +1007,7 @@ class SettingsExplorer extends React.PureComponent<IExplorerProps, ISettingsExpl
private renderCanEditLevel(roomId: string, level: SettingLevel): React.ReactNode {
const canEdit = SettingsStore.canSetValue(this.state.editSetting, roomId, level);
const className = canEdit ? 'mx_DevTools_SettingsExplorer_mutable' : 'mx_DevTools_SettingsExplorer_immutable';
return <td className={className}><code>{canEdit.toString()}</code></td>;
return <td className={className}><code>{ canEdit.toString() }</code></td>;
}
render() {
@ -1028,17 +1028,17 @@ class SettingsExplorer extends React.PureComponent<IExplorerProps, ISettingsExpl
<table>
<thead>
<tr>
<th>{_t("Setting ID")}</th>
<th>{_t("Value")}</th>
<th>{_t("Value in this room")}</th>
<th>{ _t("Setting ID") }</th>
<th>{ _t("Value") }</th>
<th>{ _t("Value in this room") }</th>
</tr>
</thead>
<tbody>
{allSettings.map(i => (
{ allSettings.map(i => (
<tr key={i}>
<td>
<a href="" onClick={(e) => this.onViewClick(e, i)}>
<code>{i}</code>
<code>{ i }</code>
</a>
<a href="" onClick={(e) => this.onEditClick(e, i)}
className='mx_DevTools_SettingsExplorer_edit'
@ -1047,20 +1047,20 @@ class SettingsExplorer extends React.PureComponent<IExplorerProps, ISettingsExpl
</a>
</td>
<td>
<code>{this.renderSettingValue(SettingsStore.getValue(i))}</code>
<code>{ this.renderSettingValue(SettingsStore.getValue(i)) }</code>
</td>
<td>
<code>
{this.renderSettingValue(SettingsStore.getValue(i, room.roomId))}
{ this.renderSettingValue(SettingsStore.getValue(i, room.roomId)) }
</code>
</td>
</tr>
))}
)) }
</tbody>
</table>
</div>
<div className="mx_Dialog_buttons">
<button onClick={this.onBack}>{_t("Back")}</button>
<button onClick={this.onBack}>{ _t("Back") }</button>
</div>
</div>
);
@ -1068,36 +1068,36 @@ class SettingsExplorer extends React.PureComponent<IExplorerProps, ISettingsExpl
return (
<div>
<div className="mx_Dialog_content mx_DevTools_SettingsExplorer">
<h3>{_t("Setting:")} <code>{this.state.editSetting}</code></h3>
<h3>{ _t("Setting:") } <code>{ this.state.editSetting }</code></h3>
<div className='mx_DevTools_SettingsExplorer_warning'>
<b>{_t("Caution:")}</b> {_t(
<b>{ _t("Caution:") }</b> { _t(
"This UI does NOT check the types of the values. Use at your own risk.",
)}
) }
</div>
<div>
{_t("Setting definition:")}
<pre><code>{JSON.stringify(SETTINGS[this.state.editSetting], null, 4)}</code></pre>
{ _t("Setting definition:") }
<pre><code>{ JSON.stringify(SETTINGS[this.state.editSetting], null, 4) }</code></pre>
</div>
<div>
<table>
<thead>
<tr>
<th>{_t("Level")}</th>
<th>{_t("Settable at global")}</th>
<th>{_t("Settable at room")}</th>
<th>{ _t("Level") }</th>
<th>{ _t("Settable at global") }</th>
<th>{ _t("Settable at room") }</th>
</tr>
</thead>
<tbody>
{LEVEL_ORDER.map(lvl => (
{ LEVEL_ORDER.map(lvl => (
<tr key={lvl}>
<td><code>{lvl}</code></td>
{this.renderCanEditLevel(null, lvl)}
{this.renderCanEditLevel(room.roomId, lvl)}
<td><code>{ lvl }</code></td>
{ this.renderCanEditLevel(null, lvl) }
{ this.renderCanEditLevel(room.roomId, lvl) }
</tr>
))}
)) }
</tbody>
</table>
</div>
@ -1122,8 +1122,8 @@ class SettingsExplorer extends React.PureComponent<IExplorerProps, ISettingsExpl
</div>
<div className="mx_Dialog_buttons">
<button onClick={this.onSaveClick}>{_t("Save setting values")}</button>
<button onClick={this.onBack}>{_t("Back")}</button>
<button onClick={this.onSaveClick}>{ _t("Save setting values") }</button>
<button onClick={this.onBack}>{ _t("Back") }</button>
</div>
</div>
);
@ -1131,39 +1131,39 @@ class SettingsExplorer extends React.PureComponent<IExplorerProps, ISettingsExpl
return (
<div>
<div className="mx_Dialog_content mx_DevTools_SettingsExplorer">
<h3>{_t("Setting:")} <code>{this.state.viewSetting}</code></h3>
<h3>{ _t("Setting:") } <code>{ this.state.viewSetting }</code></h3>
<div>
{_t("Setting definition:")}
<pre><code>{JSON.stringify(SETTINGS[this.state.viewSetting], null, 4)}</code></pre>
{ _t("Setting definition:") }
<pre><code>{ JSON.stringify(SETTINGS[this.state.viewSetting], null, 4) }</code></pre>
</div>
<div>
{_t("Value:")}&nbsp;
<code>{this.renderSettingValue(
{ _t("Value:") }&nbsp;
<code>{ this.renderSettingValue(
SettingsStore.getValue(this.state.viewSetting),
)}</code>
) }</code>
</div>
<div>
{_t("Value in this room:")}&nbsp;
<code>{this.renderSettingValue(
{ _t("Value in this room:") }&nbsp;
<code>{ this.renderSettingValue(
SettingsStore.getValue(this.state.viewSetting, room.roomId),
)}</code>
) }</code>
</div>
<div>
{_t("Values at explicit levels:")}
<pre><code>{this.renderExplicitSettingValues(
{ _t("Values at explicit levels:") }
<pre><code>{ this.renderExplicitSettingValues(
this.state.viewSetting, null,
)}</code></pre>
) }</code></pre>
</div>
<div>
{_t("Values at explicit levels in this room:")}
<pre><code>{this.renderExplicitSettingValues(
{ _t("Values at explicit levels in this room:") }
<pre><code>{ this.renderExplicitSettingValues(
this.state.viewSetting, room.roomId,
)}</code></pre>
) }</code></pre>
</div>
</div>
@ -1171,7 +1171,7 @@ class SettingsExplorer extends React.PureComponent<IExplorerProps, ISettingsExpl
<button onClick={(e) => this.onEditClick(e, this.state.viewSetting)}>{
_t("Edit Values")
}</button>
<button onClick={this.onBack}>{_t("Back")}</button>
<button onClick={this.onBack}>{ _t("Back") }</button>
</div>
</div>
);
@ -1232,12 +1232,12 @@ export default class DevtoolsDialog extends React.PureComponent<IProps, IState>
if (this.state.mode) {
body = <MatrixClientContext.Consumer>
{(cli) => <React.Fragment>
{ (cli) => <React.Fragment>
<div className="mx_DevTools_label_left">{ this.state.mode.getLabel() }</div>
<div className="mx_DevTools_label_right">Room ID: { this.props.roomId }</div>
<div className="mx_DevTools_label_bottom" />
<this.state.mode onBack={this.onBack} room={cli.getRoom(this.props.roomId)} />
</React.Fragment>}
</React.Fragment> }
</MatrixClientContext.Consumer>;
} else {
const classes = "mx_DevTools_RoomStateExplorer_button";

View file

@ -151,16 +151,16 @@ export default class EditCommunityPrototypeDialog extends React.PureComponent<IP
<AccessibleButton
onClick={this.onChangeAvatar}
className="mx_EditCommunityPrototypeDialog_avatarContainer"
>{preview}</AccessibleButton>
>{ preview }</AccessibleButton>
<div className="mx_EditCommunityPrototypeDialog_tip">
<b>{_t("Add image (optional)")}</b>
<b>{ _t("Add image (optional)") }</b>
<span>
{_t("An image will help people identify your community.")}
{ _t("An image will help people identify your community.") }
</span>
</div>
</div>
<AccessibleButton kind="primary" onClick={this.onSubmit} disabled={this.state.busy}>
{_t("Save")}
{ _t("Save") }
</AccessibleButton>
</div>
</form>

View file

@ -58,10 +58,10 @@ export default (props) => {
countlyFeedbackSection = <React.Fragment>
<hr />
<div className="mx_FeedbackDialog_section mx_FeedbackDialog_rateApp">
<h3>{_t("Rate %(brand)s", { brand })}</h3>
<h3>{ _t("Rate %(brand)s", { brand }) }</h3>
<p>{_t("Tell us below how you feel about %(brand)s so far.", { brand })}</p>
<p>{_t("Please go into as much detail as you like, so we can track down the problem.")}</p>
<p>{ _t("Tell us below how you feel about %(brand)s so far.", { brand }) }</p>
<p>{ _t("Please go into as much detail as you like, so we can track down the problem.") }</p>
<StyledRadioGroup
name="feedbackRating"
@ -95,7 +95,7 @@ export default (props) => {
let subheading;
if (hasFeedback) {
subheading = (
<h2>{_t("There are two ways you can provide feedback and help us improve %(brand)s.", { brand })}</h2>
<h2>{ _t("There are two ways you can provide feedback and help us improve %(brand)s.", { brand }) }</h2>
);
}
@ -106,7 +106,7 @@ export default (props) => {
_t("PRO TIP: If you start a bug, please submit <debugLogsLink>debug logs</debugLogsLink> " +
"to help us track down the problem.", {}, {
debugLogsLink: sub => (
<AccessibleButton kind="link" onClick={onDebugLogsLinkClick}>{sub}</AccessibleButton>
<AccessibleButton kind="link" onClick={onDebugLogsLinkClick}>{ sub }</AccessibleButton>
),
})
}</p>
@ -121,7 +121,7 @@ export default (props) => {
{ subheading }
<div className="mx_FeedbackDialog_section mx_FeedbackDialog_reportBug">
<h3>{_t("Report a bug")}</h3>
<h3>{ _t("Report a bug") }</h3>
<p>{
_t("Please view <existingIssuesLink>existing bugs on Github</existingIssuesLink> first. " +
"No match? <newIssueLink>Start a new one</newIssueLink>.", {}, {
@ -133,7 +133,7 @@ export default (props) => {
},
})
}</p>
{bugReports}
{ bugReports }
</div>
{ countlyFeedbackSection }
</React.Fragment>}

View file

@ -43,6 +43,7 @@ import QueryMatcher from "../../../autocomplete/QueryMatcher";
import TruncatedList from "../elements/TruncatedList";
import EntityTile from "../rooms/EntityTile";
import BaseAvatar from "../avatars/BaseAvatar";
import SpaceStore from "../../../stores/SpaceStore";
const AVATAR_SIZE = 30;
@ -180,7 +181,7 @@ const ForwardDialog: React.FC<IProps> = ({ matrixClient: cli, event, permalinkCr
const [query, setQuery] = useState("");
const lcQuery = query.toLowerCase();
const spacesEnabled = useFeatureEnabled("feature_spaces");
const spacesEnabled = SpaceStore.spacesEnabled;
const flairEnabled = useFeatureEnabled(UIFeature.Flair);
const previewLayout = useSettingValue<Layout>("layout");

View file

@ -177,32 +177,32 @@ export default class HostSignupDialog extends React.PureComponent<IProps, IState
const textComponent = (
<>
<p>
{_t("Continuing temporarily allows the %(hostSignupBrand)s setup process to access your " +
{ _t("Continuing temporarily allows the %(hostSignupBrand)s setup process to access your " +
"account to fetch verified email addresses. This data is not stored.", {
hostSignupBrand: this.config.brand,
})}
}) }
</p>
<p>
{_t("Learn more in our <privacyPolicyLink />, <termsOfServiceLink /> and <cookiePolicyLink />.",
{ _t("Learn more in our <privacyPolicyLink />, <termsOfServiceLink /> and <cookiePolicyLink />.",
{},
{
cookiePolicyLink: () => (
<a href={this.config.cookiePolicyUrl} target="_blank" rel="noreferrer noopener">
{_t("Cookie Policy")}
{ _t("Cookie Policy") }
</a>
),
privacyPolicyLink: () => (
<a href={this.config.privacyPolicyUrl} target="_blank" rel="noreferrer noopener">
{_t("Privacy Policy")}
{ _t("Privacy Policy") }
</a>
),
termsOfServiceLink: () => (
<a href={this.config.termsOfServiceUrl} target="_blank" rel="noreferrer noopener">
{_t("Terms of Service")}
{ _t("Terms of Service") }
</a>
),
},
)}
) }
</p>
</>
);
@ -241,12 +241,12 @@ export default class HostSignupDialog extends React.PureComponent<IProps, IState
},
)}
>
{this.state.minimized &&
{ this.state.minimized &&
<div className="mx_Dialog_header mx_Dialog_headerWithButton">
<div className="mx_Dialog_title">
{_t("%(hostSignupBrand)s Setup", {
{ _t("%(hostSignupBrand)s Setup", {
hostSignupBrand: this.config.brand,
})}
}) }
</div>
<AccessibleButton
className="mx_HostSignup_maximize_button"
@ -256,7 +256,7 @@ export default class HostSignupDialog extends React.PureComponent<IProps, IState
/>
</div>
}
{!this.state.minimized &&
{ !this.state.minimized &&
<div className="mx_Dialog_header mx_Dialog_headerWithCancel">
<AccessibleButton
onClick={this.minimizeDialog}
@ -272,12 +272,12 @@ export default class HostSignupDialog extends React.PureComponent<IProps, IState
/>
</div>
}
{this.state.error &&
{ this.state.error &&
<div>
{this.state.error}
{ this.state.error }
</div>
}
{!this.state.error &&
{ !this.state.error &&
<iframe
src={this.config.url}
ref={this.iframeRef}

View file

@ -138,7 +138,7 @@ export default class IncomingSasDialog extends React.Component {
url={url}
width={48} height={48} resizeMethod='crop'
/>
<h2>{oppProfile.displayname}</h2>
<h2>{ oppProfile.displayname }</h2>
</div>;
} else if (this.state.opponentProfileError) {
profile = <div>
@ -146,42 +146,42 @@ export default class IncomingSasDialog extends React.Component {
idName={this.props.verifier.userId}
width={48} height={48}
/>
<h2>{this.props.verifier.userId}</h2>
<h2>{ this.props.verifier.userId }</h2>
</div>;
} else {
profile = <Spinner />;
}
const userDetailText = [
<p key="p1">{_t(
<p key="p1">{ _t(
"Verify this user to mark them as trusted. " +
"Trusting users gives you extra peace of mind when using " +
"end-to-end encrypted messages.",
)}</p>,
<p key="p2">{_t(
) }</p>,
<p key="p2">{ _t(
// NB. Below wording adjusted to singular 'session' until we have
// cross-signing
"Verifying this user will mark their session as trusted, and " +
"also mark your session as trusted to them.",
)}</p>,
) }</p>,
];
const selfDetailText = [
<p key="p1">{_t(
<p key="p1">{ _t(
"Verify this device to mark it as trusted. " +
"Trusting this device gives you and other users extra peace of mind when using " +
"end-to-end encrypted messages.",
)}</p>,
<p key="p2">{_t(
) }</p>,
<p key="p2">{ _t(
"Verifying this device will mark it as trusted, and users who have verified with " +
"you will trust this device.",
)}</p>,
) }</p>,
];
return (
<div>
{profile}
{isSelf ? selfDetailText : userDetailText}
{ profile }
{ isSelf ? selfDetailText : userDetailText }
<DialogButtons
primaryButton={_t('Continue')}
hasCancel={true}
@ -209,7 +209,7 @@ export default class IncomingSasDialog extends React.Component {
return (
<div>
<Spinner />
<p>{_t("Waiting for partner to confirm...")}</p>
<p>{ _t("Waiting for partner to confirm...") }</p>
</div>
);
}
@ -251,7 +251,7 @@ export default class IncomingSasDialog extends React.Component {
onFinished={this._onFinished}
fixedWidth={false}
>
{body}
{ body }
</BaseDialog>
);
}

View file

@ -49,7 +49,7 @@ export default class IntegrationsDisabledDialog extends React.Component {
title={_t("Integrations are disabled")}
>
<div className='mx_IntegrationsDisabledDialog_content'>
<p>{_t("Enable 'Manage Integrations' in Settings to do this.")}</p>
<p>{ _t("Enable 'Manage Integrations' in Settings to do this.") }</p>
</div>
<DialogButtons
primaryButton={_t("Settings")}

View file

@ -45,11 +45,11 @@ export default class IntegrationsImpossibleDialog extends React.Component {
>
<div className='mx_IntegrationsImpossibleDialog_content'>
<p>
{_t(
"Your %(brand)s doesn't allow you to use an Integration Manager to do this. " +
{ _t(
"Your %(brand)s doesn't allow you to use an integration manager to do this. " +
"Please contact an admin.",
{ brand },
)}
) }
</p>
</div>
<DialogButtons

View file

@ -163,7 +163,7 @@ export default class InteractiveAuthDialog extends React.Component {
} else {
content = (
<div id='mx_Dialog_content'>
{body}
{ body }
<InteractiveAuth
ref={this._collectInteractiveAuth}
matrixClient={this.props.matrixClient}

View file

@ -32,7 +32,6 @@ import Modal from "../../../Modal";
import { humanizeTime } from "../../../utils/humanize";
import createRoom, {
canEncryptToAllUsers,
ensureDMExists,
findDMForUser,
privateShouldBeEncrypted,
} from "../../../createRoom";
@ -64,9 +63,15 @@ import { copyPlaintext, selectText } from "../../../utils/strings";
import * as ContextMenu from "../../structures/ContextMenu";
import { toRightOf } from "../../structures/ContextMenu";
import GenericTextContextMenu from "../context_menus/GenericTextContextMenu";
import { TransferCallPayload } from '../../../dispatcher/payloads/TransferCallPayload';
import Field from '../elements/Field';
import TabbedView, { Tab, TabLocation } from '../../structures/TabbedView';
import Dialpad from '../voip/DialPad';
import QuestionDialog from "./QuestionDialog";
import Spinner from "../elements/Spinner";
import BaseDialog from "./BaseDialog";
import DialPadBackspaceButton from "../elements/DialPadBackspaceButton";
import SpaceStore from "../../../stores/SpaceStore";
// we have a number of types defined from the Matrix spec which can't reasonably be altered here.
/* eslint-disable camelcase */
@ -79,11 +84,19 @@ interface IRecentUser {
export const KIND_DM = "dm";
export const KIND_INVITE = "invite";
// NB. This dialog needs the 'mx_InviteDialog_transferWrapper' wrapper class to have the correct
// padding on the bottom (because all modals have 24px padding on all sides), so this needs to
// be passed when creating the modal
export const KIND_CALL_TRANSFER = "call_transfer";
const INITIAL_ROOMS_SHOWN = 3; // Number of rooms to show at first
const INCREMENT_ROOMS_SHOWN = 5; // Number of rooms to add when 'show more' is clicked
enum TabId {
UserDirectory = 'users',
DialPad = 'dialpad',
}
// This is the interface that is expected by various components in the Invite Dialog and RoomInvite.
// It is a bit awkward because it also matches the RoomMember class from the js-sdk with some extra support
// for 3PIDs/email addresses.
@ -109,11 +122,11 @@ export abstract class Member {
class DirectoryMember extends Member {
private readonly _userId: string;
private readonly displayName: string;
private readonly avatarUrl: string;
private readonly displayName?: string;
private readonly avatarUrl?: string;
// eslint-disable-next-line camelcase
constructor(userDirResult: { user_id: string, display_name: string, avatar_url: string }) {
constructor(userDirResult: { user_id: string, display_name?: string, avatar_url?: string }) {
super();
this._userId = userDirResult.user_id;
this.displayName = userDirResult.display_name;
@ -211,8 +224,8 @@ class DMUserTile extends React.PureComponent<IDMUserTileProps> {
return (
<span className='mx_InviteDialog_userTile'>
<span className='mx_InviteDialog_userTile_pill'>
{avatar}
<span className='mx_InviteDialog_userTile_name'>{this.props.member.name}</span>
{ avatar }
<span className='mx_InviteDialog_userTile_name'>{ this.props.member.name }</span>
</span>
{ closeButton }
</span>
@ -254,20 +267,20 @@ class DMRoomTile extends React.PureComponent<IDMRoomTileProps> {
// Push any text we missed (first bit/middle of text)
if (ii > i) {
// Push any text we aren't highlighting (middle of text match, or beginning of text)
result.push(<span key={i + 'begin'}>{str.substring(i, ii)}</span>);
result.push(<span key={i + 'begin'}>{ str.substring(i, ii) }</span>);
}
i = ii; // copy over ii only if we have a match (to preserve i for end-of-text matching)
// Highlight the word the user entered
const substr = str.substring(i, filterStr.length + i);
result.push(<span className='mx_InviteDialog_roomTile_highlight' key={i + 'bold'}>{substr}</span>);
result.push(<span className='mx_InviteDialog_roomTile_highlight' key={i + 'bold'}>{ substr }</span>);
i += substr.length;
}
// Push any text we missed (end of text)
if (i < str.length) {
result.push(<span key={i + 'end'}>{str.substring(i)}</span>);
result.push(<span key={i + 'end'}>{ str.substring(i) }</span>);
}
return result;
@ -277,7 +290,7 @@ class DMRoomTile extends React.PureComponent<IDMRoomTileProps> {
let timestamp = null;
if (this.props.lastActiveTs) {
const humanTs = humanizeTime(this.props.lastActiveTs);
timestamp = <span className='mx_InviteDialog_roomTile_time'>{humanTs}</span>;
timestamp = <span className='mx_InviteDialog_roomTile_time'>{ humanTs }</span>;
}
const avatarSize = 36;
@ -304,8 +317,8 @@ class DMRoomTile extends React.PureComponent<IDMRoomTileProps> {
// the browser from reloading the image source when the avatar remounts).
const stackedAvatar = (
<span className='mx_InviteDialog_roomTile_avatarStack'>
{avatar}
{checkmark}
{ avatar }
{ checkmark }
</span>
);
@ -315,12 +328,12 @@ class DMRoomTile extends React.PureComponent<IDMRoomTileProps> {
return (
<div className='mx_InviteDialog_roomTile' onClick={this.onClick}>
{stackedAvatar}
{ stackedAvatar }
<span className="mx_InviteDialog_roomTile_nameStack">
<div className='mx_InviteDialog_roomTile_name'>{this.highlightName(this.props.member.name)}</div>
<div className='mx_InviteDialog_roomTile_userId'>{caption}</div>
<div className='mx_InviteDialog_roomTile_name'>{ this.highlightName(this.props.member.name) }</div>
<div className='mx_InviteDialog_roomTile_userId'>{ caption }</div>
</span>
{timestamp}
{ timestamp }
</div>
);
}
@ -356,6 +369,8 @@ interface IInviteDialogState {
canUseIdentityServer: boolean;
tryingIdentityServer: boolean;
consultFirst: boolean;
dialPadValue: string;
currentTabId: TabId;
// These two flags are used for the 'Go' button to communicate what is going on.
busy: boolean;
@ -370,7 +385,7 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
};
private closeCopiedTooltip: () => void;
private debounceTimer: NodeJS.Timeout = null; // actually number because we're in the browser
private debounceTimer: number = null; // actually number because we're in the browser
private editorRef = createRef<HTMLInputElement>();
private unmounted = false;
@ -407,6 +422,8 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
canUseIdentityServer: !!MatrixClientPeg.get().getIdentityServerUrl(),
tryingIdentityServer: false,
consultFirst: false,
dialPadValue: '',
currentTabId: TabId.UserDirectory,
// These two flags are used for the 'Go' button to communicate what is going on.
busy: false,
@ -768,44 +785,32 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
};
private transferCall = async () => {
this.convertFilter();
const targets = this.convertFilter();
const targetIds = targets.map(t => t.userId);
if (targetIds.length > 1) {
this.setState({
errorText: _t("A call can only be transferred to a single user."),
});
}
if (this.state.consultFirst) {
const dmRoomId = await ensureDMExists(MatrixClientPeg.get(), targetIds[0]);
dis.dispatch({
action: 'place_call',
type: this.props.call.type,
room_id: dmRoomId,
transferee: this.props.call,
});
dis.dispatch({
action: 'view_room',
room_id: dmRoomId,
should_peek: false,
joining: false,
});
this.props.onFinished();
} else {
this.setState({ busy: true });
try {
await this.props.call.transfer(targetIds[0]);
this.setState({ busy: false });
this.props.onFinished();
} catch (e) {
if (this.state.currentTabId == TabId.UserDirectory) {
this.convertFilter();
const targets = this.convertFilter();
const targetIds = targets.map(t => t.userId);
if (targetIds.length > 1) {
this.setState({
busy: false,
errorText: _t("Failed to transfer call"),
errorText: _t("A call can only be transferred to a single user."),
});
return;
}
dis.dispatch({
action: Action.TransferCallToMatrixID,
call: this.props.call,
destination: targetIds[0],
consultFirst: this.state.consultFirst,
} as TransferCallPayload);
} else {
dis.dispatch({
action: Action.TransferCallToPhoneNumber,
call: this.props.call,
destination: this.state.dialPadValue,
consultFirst: this.state.consultFirst,
} as TransferCallPayload);
}
this.props.onFinished();
};
private onKeyDown = (e) => {
@ -827,6 +832,10 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
}
};
private onCancel = () => {
this.props.onFinished([]);
};
private updateSuggestions = async (term) => {
MatrixClientPeg.get().searchUserDirectory({ term }).then(async r => {
if (term !== this.state.filterText) {
@ -962,11 +971,14 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
private toggleMember = (member: Member) => {
if (!this.state.busy) {
let filterText = this.state.filterText;
const targets = this.state.targets.map(t => t); // cheap clone for mutation
let targets = this.state.targets.map(t => t); // cheap clone for mutation
const idx = targets.indexOf(member);
if (idx >= 0) {
targets.splice(idx, 1);
} else {
if (this.props.kind === KIND_CALL_TRANSFER && targets.length > 0) {
targets = [];
}
targets.push(member);
filterText = ""; // clear the filter when the user accepts a suggestion
}
@ -1140,8 +1152,8 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
if (sourceMembers.length === 0 && !hasAdditionalMembers) {
return (
<div className='mx_InviteDialog_section'>
<h3>{sectionName}</h3>
<p>{_t("No results")}</p>
<h3>{ sectionName }</h3>
<p>{ _t("No results") }</p>
</div>
);
}
@ -1163,7 +1175,7 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
if (hasMore) {
showMore = (
<AccessibleButton onClick={showMoreFn} kind="link">
{_t("Show more")}
{ _t("Show more") }
</AccessibleButton>
);
}
@ -1180,15 +1192,20 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
));
return (
<div className='mx_InviteDialog_section'>
<h3>{sectionName}</h3>
{sectionSubname ? <p className="mx_InviteDialog_subname">{sectionSubname}</p> : null}
{tiles}
{showMore}
<h3>{ sectionName }</h3>
{ sectionSubname ? <p className="mx_InviteDialog_subname">{ sectionSubname }</p> : null }
{ tiles }
{ showMore }
</div>
);
}
private renderEditor() {
const hasPlaceholder = (
this.props.kind == KIND_CALL_TRANSFER &&
this.state.targets.length === 0 &&
this.state.filterText.length === 0
);
const targets = this.state.targets.map(t => (
<DMUserTile member={t} onRemove={!this.state.busy && this.removeMember} key={t.userId} />
));
@ -1201,14 +1218,15 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
ref={this.editorRef}
onPaste={this.onPaste}
autoFocus={true}
disabled={this.state.busy}
disabled={this.state.busy || (this.props.kind == KIND_CALL_TRANSFER && this.state.targets.length > 0)}
autoComplete="off"
placeholder={hasPlaceholder ? _t("Search") : null}
/>
);
return (
<div className='mx_InviteDialog_editor' onClick={this.onClickInputArea}>
{targets}
{input}
{ targets }
{ input }
</div>
);
}
@ -1223,7 +1241,7 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
const defaultIdentityServerUrl = getDefaultIdentityServerUrl();
if (defaultIdentityServerUrl) {
return (
<div className="mx_AddressPickerDialog_identityServer">{_t(
<div className="mx_AddressPickerDialog_identityServer">{ _t(
"Use an identity server to invite by email. " +
"<default>Use the default (%(defaultIdentityServerName)s)</default> " +
"or manage in <settings>Settings</settings>.",
@ -1231,24 +1249,46 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
defaultIdentityServerName: abbreviateUrl(defaultIdentityServerUrl),
},
{
default: sub => <a href="#" onClick={this.onUseDefaultIdentityServerClick}>{sub}</a>,
settings: sub => <a href="#" onClick={this.onManageSettingsClick}>{sub}</a>,
default: sub => <a href="#" onClick={this.onUseDefaultIdentityServerClick}>{ sub }</a>,
settings: sub => <a href="#" onClick={this.onManageSettingsClick}>{ sub }</a>,
},
)}</div>
) }</div>
);
} else {
return (
<div className="mx_AddressPickerDialog_identityServer">{_t(
<div className="mx_AddressPickerDialog_identityServer">{ _t(
"Use an identity server to invite by email. " +
"Manage in <settings>Settings</settings>.",
{}, {
settings: sub => <a href="#" onClick={this.onManageSettingsClick}>{sub}</a>,
settings: sub => <a href="#" onClick={this.onManageSettingsClick}>{ sub }</a>,
},
)}</div>
) }</div>
);
}
}
private onDialFormSubmit = ev => {
ev.preventDefault();
this.transferCall();
};
private onDialChange = ev => {
this.setState({ dialPadValue: ev.currentTarget.value });
};
private onDigitPress = digit => {
this.setState({ dialPadValue: this.state.dialPadValue + digit });
};
private onDeletePress = () => {
if (this.state.dialPadValue.length === 0) return;
this.setState({ dialPadValue: this.state.dialPadValue.slice(0, -1) });
};
private onTabChange = (tabId: TabId) => {
this.setState({ currentTabId: tabId });
};
private async onLinkClick(e) {
e.preventDefault();
selectText(e.target);
@ -1278,12 +1318,16 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
let helpText;
let buttonText;
let goButtonFn;
let consultConnectSection;
let extraSection;
let footer;
let keySharingWarning = <span />;
const identityServersEnabled = SettingsStore.getValue(UIFeature.IdentityServer);
const hasSelection = this.state.targets.length > 0
|| (this.state.filterText && this.state.filterText.includes('@'));
const cli = MatrixClientPeg.get();
const userId = cli.getUserId();
if (this.props.kind === KIND_DM) {
@ -1295,7 +1339,7 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
{},
{ userId: () => {
return (
<a href={makeUserPermalink(userId)} rel="noreferrer noopener" target="_blank">{userId}</a>
<a href={makeUserPermalink(userId)} rel="noreferrer noopener" target="_blank">{ userId }</a>
);
} },
);
@ -1305,7 +1349,7 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
{},
{ userId: () => {
return (
<a href={makeUserPermalink(userId)} rel="noreferrer noopener" target="_blank">{userId}</a>
<a href={makeUserPermalink(userId)} rel="noreferrer noopener" target="_blank">{ userId }</a>
);
} },
);
@ -1323,7 +1367,7 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
href={makeUserPermalink(userId)}
rel="noreferrer noopener"
target="_blank"
>{userId}</a>
>{ userId }</a>
);
},
a: (sub) => {
@ -1331,13 +1375,13 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
<AccessibleButton
kind="link"
onClick={this.onCommunityInviteClick}
>{sub}</AccessibleButton>
>{ sub }</AccessibleButton>
);
},
},
);
helpText = <React.Fragment>
{ helpText } {inviteText}
{ helpText } { inviteText }
</React.Fragment>;
}
buttonText = _t("Go");
@ -1364,7 +1408,7 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
</div>;
} else if (this.props.kind === KIND_INVITE) {
const room = MatrixClientPeg.get()?.getRoom(this.props.roomId);
const isSpace = SettingsStore.getValue("feature_spaces") && room?.isSpaceRoom();
const isSpace = SpaceStore.spacesEnabled && room?.isSpaceRoom();
title = isSpace
? _t("Invite to %(spaceName)s", {
spaceName: room.name || _t("Unnamed Space"),
@ -1394,9 +1438,9 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
helpText = _t(helpTextUntranslated, {}, {
userId: () =>
<a href={makeUserPermalink(userId)} rel="noreferrer noopener" target="_blank">{userId}</a>,
<a href={makeUserPermalink(userId)} rel="noreferrer noopener" target="_blank">{ userId }</a>,
a: (sub) =>
<a href={makeRoomPermalink(this.props.roomId)} rel="noreferrer noopener" target="_blank">{sub}</a>,
<a href={makeRoomPermalink(this.props.roomId)} rel="noreferrer noopener" target="_blank">{ sub }</a>,
});
buttonText = _t("Invite");
@ -1415,29 +1459,122 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
<img
src={require("../../../../res/img/element-icons/info.svg")}
width={14} height={14} />
{" " + _t("Invited people will be able to read old messages.")}
{ " " + _t("Invited people will be able to read old messages.") }
</p>;
}
}
} else if (this.props.kind === KIND_CALL_TRANSFER) {
title = _t("Transfer");
buttonText = _t("Transfer");
goButtonFn = this.transferCall;
footer = <div>
consultConnectSection = <div className="mx_InviteDialog_transferConsultConnect">
<label>
<input type="checkbox" checked={this.state.consultFirst} onChange={this.onConsultFirstChange} />
{_t("Consult first")}
{ _t("Consult first") }
</label>
<AccessibleButton
kind="secondary"
onClick={this.onCancel}
className='mx_InviteDialog_transferConsultConnect_pushRight'
>
{ _t("Cancel") }
</AccessibleButton>
<AccessibleButton
kind="primary"
onClick={this.transferCall}
className='mx_InviteDialog_transferButton'
disabled={!hasSelection && this.state.dialPadValue === ''}
>
{ _t("Transfer") }
</AccessibleButton>
</div>;
} else {
console.error("Unknown kind of InviteDialog: " + this.props.kind);
}
const hasSelection = this.state.targets.length > 0
|| (this.state.filterText && this.state.filterText.includes('@'));
const goButton = this.props.kind == KIND_CALL_TRANSFER ? null : <AccessibleButton
kind="primary"
onClick={goButtonFn}
className='mx_InviteDialog_goButton'
disabled={this.state.busy || !hasSelection}
>
{ buttonText }
</AccessibleButton>;
const usersSection = <React.Fragment>
<p className='mx_InviteDialog_helpText'>{ helpText }</p>
<div className='mx_InviteDialog_addressBar'>
{ this.renderEditor() }
<div className='mx_InviteDialog_buttonAndSpinner'>
{ goButton }
{ spinner }
</div>
</div>
{ keySharingWarning }
{ this.renderIdentityServerWarning() }
<div className='error'>{ this.state.errorText }</div>
<div className='mx_InviteDialog_userSections'>
{ this.renderSection('recents') }
{ this.renderSection('suggestions') }
{ extraSection }
</div>
{ footer }
</React.Fragment>;
let dialogContent;
if (this.props.kind === KIND_CALL_TRANSFER) {
const tabs = [];
tabs.push(new Tab(
TabId.UserDirectory, _td("User Directory"), 'mx_InviteDialog_userDirectoryIcon', usersSection,
));
const backspaceButton = (
<DialPadBackspaceButton onBackspacePress={this.onDeletePress} />
);
// Only show the backspace button if the field has content
let dialPadField;
if (this.state.dialPadValue.length !== 0) {
dialPadField = <Field className="mx_InviteDialog_dialPadField" id="dialpad_number"
value={this.state.dialPadValue}
autoFocus={true}
onChange={this.onDialChange}
postfixComponent={backspaceButton}
/>;
} else {
dialPadField = <Field className="mx_InviteDialog_dialPadField" id="dialpad_number"
value={this.state.dialPadValue}
autoFocus={true}
onChange={this.onDialChange}
/>;
}
const dialPadSection = <div className="mx_InviteDialog_dialPad">
<form onSubmit={this.onDialFormSubmit}>
{ dialPadField }
</form>
<Dialpad hasDial={false}
onDigitPress={this.onDigitPress} onDeletePress={this.onDeletePress}
/>
</div>;
tabs.push(new Tab(TabId.DialPad, _td("Dial pad"), 'mx_InviteDialog_dialPadIcon', dialPadSection));
dialogContent = <React.Fragment>
<TabbedView tabs={tabs} initialTabId={this.state.currentTabId}
tabLocation={TabLocation.TOP} onChange={this.onTabChange}
/>
{ consultConnectSection }
</React.Fragment>;
} else {
dialogContent = <React.Fragment>
{ usersSection }
{ consultConnectSection }
</React.Fragment>;
}
return (
<BaseDialog
className={classNames("mx_InviteDialog", {
className={classNames({
mx_InviteDialog_transfer: this.props.kind === KIND_CALL_TRANSFER,
mx_InviteDialog_other: this.props.kind !== KIND_CALL_TRANSFER,
mx_InviteDialog_hasFooter: !!footer,
})}
hasCancel={true}
@ -1445,30 +1582,7 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
title={title}
>
<div className='mx_InviteDialog_content'>
<p className='mx_InviteDialog_helpText'>{helpText}</p>
<div className='mx_InviteDialog_addressBar'>
{this.renderEditor()}
<div className='mx_InviteDialog_buttonAndSpinner'>
<AccessibleButton
kind="primary"
onClick={goButtonFn}
className='mx_InviteDialog_goButton'
disabled={this.state.busy || !hasSelection}
>
{buttonText}
</AccessibleButton>
{spinner}
</div>
</div>
{keySharingWarning}
{this.renderIdentityServerWarning()}
<div className='error'>{this.state.errorText}</div>
<div className='mx_InviteDialog_userSections'>
{this.renderSection('recents')}
{this.renderSection('suggestions')}
{extraSection}
</div>
{footer}
{ dialogContent }
</div>
</BaseDialog>
);

View file

@ -69,10 +69,10 @@ export default function KeySignatureUploadFailedDialog({
const brand = SdkConfig.get().brand;
body = (<div>
<p>{_t("%(brand)s encountered an error during upload of:", { brand })}</p>
<p>{reason}</p>
{retrying && <Spinner />}
<pre>{JSON.stringify(failures, null, 2)}</pre>
<p>{ _t("%(brand)s encountered an error during upload of:", { brand }) }</p>
<p>{ reason }</p>
{ retrying && <Spinner /> }
<pre>{ JSON.stringify(failures, null, 2) }</pre>
<DialogButtons
primaryButton='Retry'
hasCancel={true}
@ -83,11 +83,11 @@ export default function KeySignatureUploadFailedDialog({
</div>);
} else {
body = (<div>
{success ?
<span>{_t("Upload completed")}</span> :
{ success ?
<span>{ _t("Upload completed") }</span> :
cancelled ?
<span>{_t("Cancelled signature upload")}</span> :
<span>{_t("Unable to upload")}</span>}
<span>{ _t("Cancelled signature upload") }</span> :
<span>{ _t("Unable to upload") }</span> }
<DialogButtons
primaryButton={_t("OK")}
hasCancel={false}
@ -104,7 +104,7 @@ export default function KeySignatureUploadFailedDialog({
fixedWidth={false}
onFinished={() => {}}
>
{body}
{ body }
</BaseDialog>
);
}

View file

@ -44,7 +44,7 @@ export default (props) => {
return (<QuestionDialog
hasCancelButton={false}
title={_t("Incompatible local cache")}
description={<div><p>{description1}</p><p>{description2}</p></div>}
description={<div><p>{ description1 }</p><p>{ description2 }</p></div>}
button={_t("Clear cache and resync")}
onFinished={props.onFinished}
/>);

View file

@ -33,7 +33,7 @@ export default (props) => {
return (<QuestionDialog
hasCancelButton={false}
title={_t("Updating %(brand)s", { brand })}
description={<div>{description}</div>}
description={<div>{ description }</div>}
button={_t("OK")}
onFinished={props.onFinished}
/>);

View file

@ -123,11 +123,11 @@ export default class LogoutDialog extends React.Component {
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
const description = <div>
<p>{_t(
<p>{ _t(
"Encrypted messages are secured with end-to-end encryption. " +
"Only you and the recipient(s) have the keys to read these messages.",
)}</p>
<p>{_t("Back up your keys before signing out to avoid losing them.")}</p>
) }</p>
<p>{ _t("Back up your keys before signing out to avoid losing them.") }</p>
</div>;
let dialogContent;
@ -156,13 +156,13 @@ export default class LogoutDialog extends React.Component {
focus={true}
>
<button onClick={this._onLogoutConfirm}>
{_t("I don't want my encrypted messages")}
{ _t("I don't want my encrypted messages") }
</button>
</DialogButtons>
<details>
<summary>{_t("Advanced")}</summary>
<summary>{ _t("Advanced") }</summary>
<p><button onClick={this._onExportE2eKeysClicked}>
{_t("Manually export keys")}
{ _t("Manually export keys") }
</button></p>
</details>
</div>;
@ -176,7 +176,7 @@ export default class LogoutDialog extends React.Component {
hasCancel={true}
onFinished={this._onFinished}
>
{dialogContent}
{ dialogContent }
</BaseDialog>);
} else {
const QuestionDialog = sdk.getComponent('views.dialogs.QuestionDialog');

View file

@ -134,18 +134,18 @@ export default class MessageEditHistoryDialog extends React.PureComponent {
const { error } = this.state;
if (error.errcode === "M_UNRECOGNIZED") {
content = (<p className="mx_MessageEditHistoryDialog_error">
{_t("Your homeserver doesn't seem to support this feature.")}
{ _t("Your homeserver doesn't seem to support this feature.") }
</p>);
} else if (error.errcode) {
// some kind of error from the homeserver
content = (<p className="mx_MessageEditHistoryDialog_error">
{_t("Something went wrong!")}
{ _t("Something went wrong!") }
</p>);
} else {
content = (<p className="mx_MessageEditHistoryDialog_error">
{_t("Cannot reach homeserver")}
{ _t("Cannot reach homeserver") }
<br />
{_t("Ensure you have a stable internet connection, or get in touch with the server admin")}
{ _t("Ensure you have a stable internet connection, or get in touch with the server admin") }
</p>);
}
} else if (this.state.isLoading) {
@ -155,11 +155,11 @@ export default class MessageEditHistoryDialog extends React.PureComponent {
const ScrollPanel = sdk.getComponent("structures.ScrollPanel");
content = (<ScrollPanel
className="mx_MessageEditHistoryDialog_scrollPanel"
onFillRequest={ this.loadMoreEdits }
onFillRequest={this.loadMoreEdits}
stickyBottom={false}
startAtBottom={false}
>
<ul className="mx_MessageEditHistoryDialog_edits">{this._renderEdits()}</ul>
<ul className="mx_MessageEditHistoryDialog_edits">{ this._renderEdits() }</ul>
</ScrollPanel>);
}
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
@ -170,7 +170,7 @@ export default class MessageEditHistoryDialog extends React.PureComponent {
onFinished={this.props.onFinished}
title={_t("Message edits")}
>
{content}
{ content }
</BaseDialog>
);
}

View file

@ -191,9 +191,9 @@ export default class ModalWidgetDialog extends React.PureComponent<IProps, IStat
width="16"
alt=""
/>
{_t("Data on this screen is shared with %(widgetDomain)s", {
{ _t("Data on this screen is shared with %(widgetDomain)s", {
widgetDomain: parsed.hostname,
})}
}) }
</div>
<div>
<iframe

View file

@ -67,10 +67,10 @@ const RegistrationEmailPromptDialog: React.FC<IProps> = ({ onFinished }) => {
fixedWidth={false}
>
<div className="mx_Dialog_content" id="mx_RegistrationEmailPromptDialog">
<p>{_t("Just a heads up, if you don't add an email and forget your password, you could " +
<p>{ _t("Just a heads up, if you don't add an email and forget your password, you could " +
"<b>permanently lose access to your account</b>.", {}, {
b: sub => <b>{sub}</b>,
})}</p>
b: sub => <b>{ sub }</b>,
}) }</p>
<form onSubmit={onSubmit}>
<Field
ref={fieldRef}

View file

@ -40,7 +40,7 @@ interface IState {
busy: boolean;
err?: string;
// If we know it, the nature of the abuse, as specified by MSC3215.
nature?: EXTENDED_NATURE;
nature?: ExtendedNature;
}
const MODERATED_BY_STATE_EVENT_TYPE = [
@ -55,22 +55,22 @@ const MODERATED_BY_STATE_EVENT_TYPE = [
const ABUSE_EVENT_TYPE = "org.matrix.msc3215.abuse.report";
// Standard abuse natures.
enum NATURE {
DISAGREEMENT = "org.matrix.msc3215.abuse.nature.disagreement",
TOXIC = "org.matrix.msc3215.abuse.nature.toxic",
ILLEGAL = "org.matrix.msc3215.abuse.nature.illegal",
SPAM = "org.matrix.msc3215.abuse.nature.spam",
OTHER = "org.matrix.msc3215.abuse.nature.other",
enum Nature {
Disagreement = "org.matrix.msc3215.abuse.nature.disagreement",
Toxic = "org.matrix.msc3215.abuse.nature.toxic",
Illegal = "org.matrix.msc3215.abuse.nature.illegal",
Spam = "org.matrix.msc3215.abuse.nature.spam",
Other = "org.matrix.msc3215.abuse.nature.other",
}
enum NON_STANDARD_NATURE {
enum NonStandardValue {
// Non-standard abuse nature.
// It should never leave the client - we use it to fallback to
// server-wide abuse reporting.
ADMIN = "non-standard.abuse.nature.admin"
Admin = "non-standard.abuse.nature.admin"
}
type EXTENDED_NATURE = NATURE | NON_STANDARD_NATURE;
type ExtendedNature = Nature | NonStandardValue;
type Moderation = {
// The id of the moderation room.
@ -170,7 +170,7 @@ export default class ReportEventDialog extends React.Component<IProps, IState> {
// The user has clicked on a nature.
private onNatureChosen = (e: React.FormEvent<HTMLInputElement>): void => {
this.setState({ nature: e.currentTarget.value as EXTENDED_NATURE });
this.setState({ nature: e.currentTarget.value as ExtendedNature });
};
// The user has clicked "cancel".
@ -187,7 +187,7 @@ export default class ReportEventDialog extends React.Component<IProps, IState> {
// We need a nature.
// If the nature is `NATURE.OTHER` or `NON_STANDARD_NATURE.ADMIN`, we also need a `reason`.
if (!this.state.nature ||
((this.state.nature == NATURE.OTHER || this.state.nature == NON_STANDARD_NATURE.ADMIN)
((this.state.nature == Nature.Other || this.state.nature == NonStandardValue.Admin)
&& !reason)
) {
this.setState({
@ -214,8 +214,8 @@ export default class ReportEventDialog extends React.Component<IProps, IState> {
try {
const client = MatrixClientPeg.get();
const ev = this.props.mxEvent;
if (this.moderation && this.state.nature != NON_STANDARD_NATURE.ADMIN) {
const nature: NATURE = this.state.nature;
if (this.moderation && this.state.nature != NonStandardValue.Admin) {
const nature: Nature = this.state.nature;
// Report to moderators through to the dedicated bot,
// as configured in the room's state events.
@ -245,7 +245,7 @@ export default class ReportEventDialog extends React.Component<IProps, IState> {
let error = null;
if (this.state.err) {
error = <div className="error">
{this.state.err}
{ this.state.err }
</div>;
}
@ -274,27 +274,27 @@ export default class ReportEventDialog extends React.Component<IProps, IState> {
const homeServerName = SdkConfig.get()["validated_server_config"].hsName;
let subtitle;
switch (this.state.nature) {
case NATURE.DISAGREEMENT:
case Nature.Disagreement:
subtitle = _t("What this user is writing is wrong.\n" +
"This will be reported to the room moderators.");
break;
case NATURE.TOXIC:
case Nature.Toxic:
subtitle = _t("This user is displaying toxic behaviour, " +
"for instance by insulting other users or sharing " +
" adult-only content in a family-friendly room " +
" or otherwise violating the rules of this room.\n" +
"This will be reported to the room moderators.");
break;
case NATURE.ILLEGAL:
case Nature.Illegal:
subtitle = _t("This user is displaying illegal behaviour, " +
"for instance by doxing people or threatening violence.\n" +
"This will be reported to the room moderators who may escalate this to legal authorities.");
break;
case NATURE.SPAM:
case Nature.Spam:
subtitle = _t("This user is spamming the room with ads, links to ads or to propaganda.\n" +
"This will be reported to the room moderators.");
break;
case NON_STANDARD_NATURE.ADMIN:
case NonStandardValue.Admin:
if (client.isRoomEncrypted(this.props.mxEvent.getRoomId())) {
subtitle = _t("This room is dedicated to illegal or toxic content " +
"or the moderators fail to moderate illegal or toxic content.\n" +
@ -308,7 +308,7 @@ export default class ReportEventDialog extends React.Component<IProps, IState> {
{ homeserver: homeServerName });
}
break;
case NATURE.OTHER:
case Nature.Other:
subtitle = _t("Any other reason. Please describe the problem.\n" +
"This will be reported to the room moderators.");
break;
@ -326,55 +326,55 @@ export default class ReportEventDialog extends React.Component<IProps, IState> {
>
<div>
<StyledRadioButton
name = "nature"
value = { NATURE.DISAGREEMENT }
checked = { this.state.nature == NATURE.DISAGREEMENT }
onChange = { this.onNatureChosen }
name="nature"
value={Nature.Disagreement}
checked={this.state.nature == Nature.Disagreement}
onChange={this.onNatureChosen}
>
{_t('Disagree')}
{ _t('Disagree') }
</StyledRadioButton>
<StyledRadioButton
name = "nature"
value = { NATURE.TOXIC }
checked = { this.state.nature == NATURE.TOXIC }
onChange = { this.onNatureChosen }
name="nature"
value={Nature.Toxic}
checked={this.state.nature == Nature.Toxic}
onChange={this.onNatureChosen}
>
{_t('Toxic Behaviour')}
{ _t('Toxic Behaviour') }
</StyledRadioButton>
<StyledRadioButton
name = "nature"
value = { NATURE.ILLEGAL }
checked = { this.state.nature == NATURE.ILLEGAL }
onChange = { this.onNatureChosen }
name="nature"
value={Nature.Illegal}
checked={this.state.nature == Nature.Illegal}
onChange={this.onNatureChosen}
>
{_t('Illegal Content')}
{ _t('Illegal Content') }
</StyledRadioButton>
<StyledRadioButton
name = "nature"
value = { NATURE.SPAM }
checked = { this.state.nature == NATURE.SPAM }
onChange = { this.onNatureChosen }
name="nature"
value={Nature.Spam}
checked={this.state.nature == Nature.Spam}
onChange={this.onNatureChosen}
>
{_t('Spam or propaganda')}
{ _t('Spam or propaganda') }
</StyledRadioButton>
<StyledRadioButton
name = "nature"
value = { NON_STANDARD_NATURE.ADMIN }
checked = { this.state.nature == NON_STANDARD_NATURE.ADMIN }
onChange = { this.onNatureChosen }
name="nature"
value={NonStandardValue.Admin}
checked={this.state.nature == NonStandardValue.Admin}
onChange={this.onNatureChosen}
>
{_t('Report the entire room')}
{ _t('Report the entire room') }
</StyledRadioButton>
<StyledRadioButton
name = "nature"
value = { NATURE.OTHER }
checked = { this.state.nature == NATURE.OTHER }
onChange = { this.onNatureChosen }
name="nature"
value={Nature.Other}
checked={this.state.nature == Nature.Other}
onChange={this.onNatureChosen}
>
{_t('Other')}
{ _t('Other') }
</StyledRadioButton>
<p>
{subtitle}
{ subtitle }
</p>
<Field
className="mx_ReportEventDialog_reason"
@ -385,8 +385,8 @@ export default class ReportEventDialog extends React.Component<IProps, IState> {
value={this.state.reason}
disabled={this.state.busy}
/>
{progress}
{error}
{ progress }
{ error }
</div>
<DialogButtons
primaryButton={_t("Send report")}
@ -416,7 +416,7 @@ export default class ReportEventDialog extends React.Component<IProps, IState> {
"or images.")
}
</p>
{adminMessage}
{ adminMessage }
<Field
className="mx_ReportEventDialog_reason"
element="textarea"
@ -426,8 +426,8 @@ export default class ReportEventDialog extends React.Component<IProps, IState> {
value={this.state.reason}
disabled={this.state.busy}
/>
{progress}
{error}
{ progress }
{ error }
</div>
<DialogButtons
primaryButton={_t("Send report")}

View file

@ -88,19 +88,19 @@ export default class RoomUpgradeDialog extends React.Component {
hasCancel={true}
>
<p>
{_t(
{ _t(
"Upgrading this room requires closing down the current " +
"instance of the room and creating a new room in its place. " +
"To give room members the best possible experience, we will:",
)}
) }
</p>
<ol>
<li>{_t("Create a new room with the same name, description and avatar")}</li>
<li>{_t("Update any local room aliases to point to the new room")}</li>
<li>{_t("Stop users from speaking in the old version of the room, and post a message advising users to move to the new room")}</li>
<li>{_t("Put a link back to the old room at the start of the new room so people can see old messages")}</li>
<li>{ _t("Create a new room with the same name, description and avatar") }</li>
<li>{ _t("Update any local room aliases to point to the new room") }</li>
<li>{ _t("Stop users from speaking in the old version of the room, and post a message advising users to move to the new room") }</li>
<li>{ _t("Put a link back to the old room at the start of the new room so people can see old messages") }</li>
</ol>
{buttons}
{ buttons }
</BaseDialog>
);
}

View file

@ -84,16 +84,16 @@ export default class RoomUpgradeWarningDialog extends React.Component {
let bugReports = (
<p>
{_t(
{ _t(
"This usually only affects how the room is processed on the server. If you're " +
"having problems with your %(brand)s, please report a bug.", { brand },
)}
) }
</p>
);
if (SdkConfig.get().bug_report_endpoint_url) {
bugReports = (
<p>
{_t(
{ _t(
"This usually only affects how the room is processed on the server. If you're " +
"having problems with your %(brand)s, please <a>report a bug</a>.",
{
@ -101,10 +101,10 @@ export default class RoomUpgradeWarningDialog extends React.Component {
},
{
"a": (sub) => {
return <a href='#' onClick={this._openBugReportDialog}>{sub}</a>;
return <a href='#' onClick={this._openBugReportDialog}>{ sub }</a>;
},
},
)}
) }
</p>
);
}
@ -119,23 +119,23 @@ export default class RoomUpgradeWarningDialog extends React.Component {
>
<div>
<p>
{_t(
{ _t(
"Upgrading a room is an advanced action and is usually recommended when a room " +
"is unstable due to bugs, missing features or security vulnerabilities.",
)}
) }
</p>
{bugReports}
{ bugReports }
<p>
{_t(
{ _t(
"You'll upgrade this room from <oldVersion /> to <newVersion />.",
{},
{
oldVersion: () => <code>{this.state.currentVersion}</code>,
newVersion: () => <code>{this.props.targetVersion}</code>,
oldVersion: () => <code>{ this.state.currentVersion }</code>,
newVersion: () => <code>{ this.props.targetVersion }</code>,
},
)}
) }
</p>
{inviteToggle}
{ inviteToggle }
</div>
<DialogButtons
primaryButton={_t("Upgrade")}

View file

@ -54,7 +54,7 @@ export default class ServerOfflineDialog extends React.PureComponent<IProps> {
const header = (
<div className="mx_ServerOfflineDialog_content_context_timeline_header">
<RoomAvatar width={24} height={24} room={c.room} />
<span>{c.room.name}</span>
<span>{ c.room.name }</span>
</div>
);
const entries = c.transactions
@ -63,26 +63,26 @@ export default class ServerOfflineDialog extends React.PureComponent<IProps> {
let button = <Spinner w={19} h={19} />;
if (t.status === TransactionStatus.Error) {
button = (
<AccessibleButton kind="link" onClick={() => t.run()}>{_t("Resend")}</AccessibleButton>
<AccessibleButton kind="link" onClick={() => t.run()}>{ _t("Resend") }</AccessibleButton>
);
}
return (
<div className="mx_ServerOfflineDialog_content_context_txn" key={`txn-${j}`}>
<span className="mx_ServerOfflineDialog_content_context_txn_desc">
{t.auditName}
{ t.auditName }
</span>
{button}
{ button }
</div>
);
});
return (
<div className="mx_ServerOfflineDialog_content_context" key={`context-${i}`}>
<div className="mx_ServerOfflineDialog_content_context_timestamp">
{formatTime(c.firstFailedTime, SettingsStore.getValue("showTwelveHourTimestamps"))}
{ formatTime(c.firstFailedTime, SettingsStore.getValue("showTwelveHourTimestamps")) }
</div>
<div className="mx_ServerOfflineDialog_content_context_timeline">
{header}
{entries}
{ header }
{ entries }
</div>
</div>
);
@ -92,7 +92,7 @@ export default class ServerOfflineDialog extends React.PureComponent<IProps> {
public render() {
let timeline = this.renderTimeline().filter(c => !!c); // remove nulls for next check
if (timeline.length === 0) {
timeline = [<div key={1}>{_t("You're all caught up.")}</div>];
timeline = [<div key={1}>{ _t("You're all caught up.") }</div>];
}
const serverName = MatrixClientPeg.getHomeserverName();
@ -103,23 +103,23 @@ export default class ServerOfflineDialog extends React.PureComponent<IProps> {
hasCancel={true}
>
<div className="mx_ServerOfflineDialog_content">
<p>{_t(
<p>{ _t(
"Your server isn't responding to some of your requests. " +
"Below are some of the most likely reasons.",
)}</p>
) }</p>
<ul>
<li>{_t("The server (%(serverName)s) took too long to respond.", { serverName })}</li>
<li>{_t("Your firewall or anti-virus is blocking the request.")}</li>
<li>{_t("A browser extension is preventing the request.")}</li>
<li>{_t("The server is offline.")}</li>
<li>{_t("The server has denied your request.")}</li>
<li>{_t("Your area is experiencing difficulties connecting to the internet.")}</li>
<li>{_t("A connection error occurred while trying to contact the server.")}</li>
<li>{_t("The server is not configured to indicate what the problem is (CORS).")}</li>
<li>{ _t("The server (%(serverName)s) took too long to respond.", { serverName }) }</li>
<li>{ _t("Your firewall or anti-virus is blocking the request.") }</li>
<li>{ _t("A browser extension is preventing the request.") }</li>
<li>{ _t("The server is offline.") }</li>
<li>{ _t("The server has denied your request.") }</li>
<li>{ _t("Your area is experiencing difficulties connecting to the internet.") }</li>
<li>{ _t("A connection error occurred while trying to contact the server.") }</li>
<li>{ _t("The server is not configured to indicate what the problem is (CORS).") }</li>
</ul>
<hr />
<h2>{_t("Recent changes that have not yet been received")}</h2>
{timeline}
<h2>{ _t("Recent changes that have not yet been received") }</h2>
{ timeline }
</div>
</BaseDialog>;
}

View file

@ -172,7 +172,7 @@ export default class ServerPickerDialog extends React.PureComponent<IProps, ISta
if (this.defaultServer.hsNameIsDifferent) {
defaultServerName = (
<TextWithTooltip class="mx_Login_underlinedServerName" tooltip={this.defaultServer.hsUrl}>
{this.defaultServer.hsName}
{ this.defaultServer.hsName }
</TextWithTooltip>
);
}
@ -187,7 +187,7 @@ export default class ServerPickerDialog extends React.PureComponent<IProps, ISta
>
<form className="mx_Dialog_content" id="mx_ServerPickerDialog" onSubmit={this.onSubmit}>
<p>
{_t("We call the places where you can host your account homeservers.")} {text}
{ _t("We call the places where you can host your account homeservers.") } { text }
</p>
<StyledRadioButton
@ -196,7 +196,7 @@ export default class ServerPickerDialog extends React.PureComponent<IProps, ISta
checked={this.state.defaultChosen}
onChange={this.onDefaultChosen}
>
{defaultServerName}
{ defaultServerName }
</StyledRadioButton>
<StyledRadioButton
@ -205,13 +205,14 @@ export default class ServerPickerDialog extends React.PureComponent<IProps, ISta
className="mx_ServerPickerDialog_otherHomeserverRadio"
checked={!this.state.defaultChosen}
onChange={this.onOtherChosen}
childrenInLabel={false}
>
<Field
type="text"
className="mx_ServerPickerDialog_otherHomeserver"
label={_t("Other homeserver")}
onChange={this.onHomeserverChange}
onClick={this.onOtherChosen}
onFocus={this.onOtherChosen}
ref={this.fieldRef}
onValidate={this.onHomeserverValidate}
value={this.state.otherHomeserver}
@ -221,16 +222,16 @@ export default class ServerPickerDialog extends React.PureComponent<IProps, ISta
/>
</StyledRadioButton>
<p>
{_t("Use your preferred Matrix homeserver if you have one, or host your own.")}
{ _t("Use your preferred Matrix homeserver if you have one, or host your own.") }
</p>
<AccessibleButton className="mx_ServerPickerDialog_continue" kind="primary" onClick={this.onSubmit}>
{_t("Continue")}
{ _t("Continue") }
</AccessibleButton>
<h4>{_t("Learn more")}</h4>
<h4>{ _t("Learn more") }</h4>
<a href="https://matrix.org/faq/#what-is-a-homeserver%3F" target="_blank" rel="noreferrer noopener">
{_t("About homeservers")}
{ _t("About homeservers") }
</a>
</form>
</BaseDialog>;

View file

@ -33,12 +33,12 @@ export default class SeshatResetDialog extends React.PureComponent<IDialogProps>
title={_t("Reset event store?")}>
<div>
<p>
{_t("You most likely do not want to reset your event index store")}
{ _t("You most likely do not want to reset your event index store") }
<br />
{_t("If you do, please note that none of your messages will be deleted, " +
{ _t("If you do, please note that none of your messages will be deleted, " +
"but the search experience might be degraded for a few moments " +
"whilst the index is recreated",
)}
) }
</p>
</div>
<DialogButtons

View file

@ -35,7 +35,7 @@ import SettingsStore from "../../../settings/SettingsStore";
import { UIFeature } from "../../../settings/UIFeature";
import { replaceableComponent } from "../../../utils/replaceableComponent";
import BaseDialog from "./BaseDialog";
import GenericTextContextMenu from "../context_menus/GenericTextContextMenu.js";
import GenericTextContextMenu from "../context_menus/GenericTextContextMenu";
const socials = [
{

View file

@ -35,16 +35,16 @@ export default ({ onFinished }) => {
const rows = [
<tr key={"_category_" + category} className="mx_SlashCommandHelpDialog_headerRow">
<td colSpan={3}>
<h2>{_t(category)}</h2>
<h2>{ _t(category) }</h2>
</td>
</tr>,
];
categories[category].forEach(cmd => {
rows.push(<tr key={cmd.command}>
<td><strong>{cmd.getCommand()}</strong></td>
<td>{cmd.args}</td>
<td>{cmd.description}</td>
<td><strong>{ cmd.getCommand() }</strong></td>
<td>{ cmd.args }</td>
<td>{ cmd.description }</td>
</tr>);
});
@ -56,7 +56,7 @@ export default ({ onFinished }) => {
title={_t("Command Help")}
description={<table>
<tbody>
{body}
{ body }
</tbody>
</table>}
hasCloseButton={true}

Some files were not shown because too many files have changed in this diff Show more