Merge branch 'develop' of github.com:matrix-org/matrix-react-sdk into spaces-jump-to-room

This commit is contained in:
Jaiwanth 2021-07-22 08:25:24 +05:30
commit 76040c652c
582 changed files with 15328 additions and 10678 deletions

View file

@ -0,0 +1,124 @@
/*
Copyright 2021 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
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 { Playback, PlaybackState } from "../../../voice/Playback";
import React, { createRef, ReactNode, RefObject } from "react";
import { UPDATE_EVENT } from "../../../stores/AsyncStore";
import PlayPauseButton from "./PlayPauseButton";
import { replaceableComponent } from "../../../utils/replaceableComponent";
import { formatBytes } from "../../../utils/FormattingUtils";
import DurationClock from "./DurationClock";
import { Key } from "../../../Keyboard";
import { _t } from "../../../languageHandler";
import SeekBar from "./SeekBar";
import PlaybackClock from "./PlaybackClock";
interface IProps {
// Playback instance to render. Cannot change during component lifecycle: create
// an all-new component instead.
playback: Playback;
mediaName: string;
}
interface IState {
playbackPhase: PlaybackState;
}
@replaceableComponent("views.audio_messages.AudioPlayer")
export default class AudioPlayer extends React.PureComponent<IProps, IState> {
private playPauseRef: RefObject<PlayPauseButton> = createRef();
private seekRef: RefObject<SeekBar> = createRef();
constructor(props: IProps) {
super(props);
this.state = {
playbackPhase: PlaybackState.Decoding, // default assumption
};
// We don't need to de-register: the class handles this for us internally
this.props.playback.on(UPDATE_EVENT, this.onPlaybackUpdate);
// Don't wait for the promise to complete - it will emit a progress update when it
// is done, and it's not meant to take long anyhow.
// noinspection JSIgnoredPromiseFromCall
this.props.playback.prepare();
}
private onPlaybackUpdate = (ev: PlaybackState) => {
this.setState({ playbackPhase: ev });
};
private onKeyDown = (ev: React.KeyboardEvent) => {
// stopPropagation() prevents the FocusComposer catch-all from triggering,
// but we need to do it on key down instead of press (even though the user
// interaction is typically on press).
if (ev.key === Key.SPACE) {
ev.stopPropagation();
this.playPauseRef.current?.toggleState();
} else if (ev.key === Key.ARROW_LEFT) {
ev.stopPropagation();
this.seekRef.current?.left();
} else if (ev.key === Key.ARROW_RIGHT) {
ev.stopPropagation();
this.seekRef.current?.right();
}
};
protected renderFileSize(): string {
const bytes = this.props.playback.sizeBytes;
if (!bytes) return null;
// Not translated here - we're just presenting the data which should already
// be translated if needed.
return `(${formatBytes(bytes)})`;
}
public render(): ReactNode {
// tabIndex=0 to ensure that the whole component becomes a tab stop, where we handle keyboard
// events for accessibility
return <div className='mx_MediaBody mx_AudioPlayer_container' tabIndex={0} onKeyDown={this.onKeyDown}>
<div className='mx_AudioPlayer_primaryContainer'>
<PlayPauseButton
playback={this.props.playback}
playbackPhase={this.state.playbackPhase}
tabIndex={-1} // prevent tabbing into the button
ref={this.playPauseRef}
/>
<div className='mx_AudioPlayer_mediaInfo'>
<span className='mx_AudioPlayer_mediaName'>
{ 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 */ }
{ this.renderFileSize() }
</div>
</div>
</div>
<div className='mx_AudioPlayer_seek'>
<SeekBar
playback={this.props.playback}
tabIndex={-1} // prevent tabbing into the bar
playbackPhase={this.state.playbackPhase}
ref={this.seekRef}
/>
<PlaybackClock playback={this.props.playback} defaultDisplaySeconds={0} />
</div>
</div>;
}
}

View file

@ -28,7 +28,7 @@ interface IState {
* Simply converts seconds into minutes and seconds. Note that hours will not be
* displayed, making it possible to see "82:29".
*/
@replaceableComponent("views.voice_messages.Clock")
@replaceableComponent("views.audio_messages.Clock")
export default class Clock extends React.Component<IProps, IState> {
public constructor(props) {
super(props);
@ -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

@ -0,0 +1,55 @@
/*
Copyright 2021 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import React from "react";
import { replaceableComponent } from "../../../utils/replaceableComponent";
import Clock from "./Clock";
import { Playback } from "../../../voice/Playback";
interface IProps {
playback: Playback;
}
interface IState {
durationSeconds: number;
}
/**
* A clock which shows a clip's maximum duration.
*/
@replaceableComponent("views.audio_messages.DurationClock")
export default class DurationClock extends React.PureComponent<IProps, IState> {
public constructor(props) {
super(props);
this.state = {
// we track the duration on state because we won't really know what the clip duration
// is until the first time update, and as a PureComponent we are trying to dedupe state
// updates as much as possible. This is just the easiest way to avoid a forceUpdate() or
// member property to track "did we get a duration".
durationSeconds: this.props.playback.clockInfo.durationSeconds,
};
this.props.playback.clockInfo.liveData.onUpdate(this.onTimeUpdate);
}
private onTimeUpdate = (time: number[]) => {
this.setState({ durationSeconds: time[1] });
};
public render() {
return <Clock seconds={this.state.durationSeconds} />;
}
}

View file

@ -1,9 +1,12 @@
/*
Copyright 2021 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
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.
@ -12,16 +15,13 @@ limitations under the License.
*/
import React from "react";
import Clock from "./Clock";
import { IRecordingUpdate, VoiceRecording } from "../../../voice/VoiceRecording";
import { replaceableComponent } from "../../../utils/replaceableComponent";
import Clock from "./Clock";
import { MarkedExecution } from "../../../utils/MarkedExecution";
import {
IRecordingUpdate,
VoiceRecording,
} from "../../../voice/VoiceRecording";
interface IProps {
recorder?: VoiceRecording;
recorder: VoiceRecording;
}
interface IState {
@ -31,7 +31,7 @@ interface IState {
/**
* A clock for a live recording.
*/
@replaceableComponent("views.voice_messages.LiveRecordingClock")
@replaceableComponent("views.audio_messages.LiveRecordingClock")
export default class LiveRecordingClock extends React.PureComponent<IProps, IState> {
private seconds = 0;
private scheduledUpdate = new MarkedExecution(

View file

@ -1,9 +1,12 @@
/*
Copyright 2021 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
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.
@ -12,16 +15,15 @@ limitations under the License.
*/
import React from "react";
import Waveform from "./Waveform";
import { IRecordingUpdate, RECORDING_PLAYBACK_SAMPLES, VoiceRecording } from "../../../voice/VoiceRecording";
import { replaceableComponent } from "../../../utils/replaceableComponent";
import { arrayFastResample } from "../../../utils/arrays";
import { percentageOf } from "../../../utils/numbers";
import Waveform from "./Waveform";
import { MarkedExecution } from "../../../utils/MarkedExecution";
import {
IRecordingUpdate,
VoiceRecording,
} from "../../../voice/VoiceRecording";
interface IProps {
recorder?: VoiceRecording;
recorder: VoiceRecording;
}
interface IState {
@ -31,7 +33,7 @@ interface IState {
/**
* A waveform which shows the waveform of a live recording
*/
@replaceableComponent("views.voice_messages.LiveRecordingWaveform")
@replaceableComponent("views.audio_messages.LiveRecordingWaveform")
export default class LiveRecordingWaveform extends React.PureComponent<IProps, IState> {
public static defaultProps = {
progress: 1,
@ -52,15 +54,18 @@ export default class LiveRecordingWaveform extends React.PureComponent<IProps, I
componentDidMount() {
this.props.recorder.liveData.onUpdate((update: IRecordingUpdate) => {
this.waveform = update.waveform;
const bars = arrayFastResample(Array.from(update.waveform), RECORDING_PLAYBACK_SAMPLES);
// The incoming data is between zero and one, but typically even screaming into a
// microphone won't send you over 0.6, so we artificially adjust the gain for the
// waveform. This results in a slightly more cinematic/animated waveform for the
// user.
this.waveform = bars.map(b => percentageOf(b, 0, 0.50));
this.scheduledUpdate.mark();
});
}
private updateWaveform() {
this.setState({
waveform: this.waveform,
});
this.setState({ waveform: this.waveform });
}
public render() {

View file

@ -21,7 +21,8 @@ import { _t } from "../../../languageHandler";
import { Playback, PlaybackState } from "../../../voice/Playback";
import classNames from "classnames";
interface IProps {
// omitted props are handled by render function
interface IProps extends Omit<React.ComponentProps<typeof AccessibleTooltipButton>, "title" | "onClick" | "disabled"> {
// Playback instance to manipulate. Cannot change during the component lifecycle.
playback: Playback;
@ -33,19 +34,25 @@ interface IProps {
* Displays a play/pause button (activating the play/pause function of the recorder)
* to be displayed in reference to a recording.
*/
@replaceableComponent("views.voice_messages.PlayPauseButton")
@replaceableComponent("views.audio_messages.PlayPauseButton")
export default class PlayPauseButton extends React.PureComponent<IProps> {
public constructor(props) {
super(props);
}
private onClick = async () => {
await this.props.playback.toggle();
private onClick = () => {
// noinspection JSIgnoredPromiseFromCall
this.toggleState();
};
public async toggleState() {
await this.props.playback.toggle();
}
public render(): ReactNode {
const isPlaying = this.props.playback.isPlaying;
const isDisabled = this.props.playbackPhase === PlaybackState.Decoding;
const { playback, playbackPhase, ...restProps } = this.props;
const isPlaying = playback.isPlaying;
const isDisabled = playbackPhase === PlaybackState.Decoding;
const classes = classNames('mx_PlayPauseButton', {
'mx_PlayPauseButton_play': !isPlaying,
'mx_PlayPauseButton_pause': isPlaying,
@ -56,6 +63,7 @@ export default class PlayPauseButton extends React.PureComponent<IProps> {
title={isPlaying ? _t("Pause") : _t("Play")}
onClick={this.onClick}
disabled={isDisabled}
{...restProps}
/>;
}
}

View file

@ -22,6 +22,11 @@ import { UPDATE_EVENT } from "../../../stores/AsyncStore";
interface IProps {
playback: Playback;
// The default number of seconds to show when the playback has completed or
// has not started. Not used during playback, even when paused. Defaults to
// clip duration length.
defaultDisplaySeconds?: number;
}
interface IState {
@ -33,7 +38,7 @@ interface IState {
/**
* A clock for a playback of a recording.
*/
@replaceableComponent("views.voice_messages.PlaybackClock")
@replaceableComponent("views.audio_messages.PlaybackClock")
export default class PlaybackClock extends React.PureComponent<IProps, IState> {
public constructor(props) {
super(props);
@ -64,7 +69,11 @@ export default class PlaybackClock extends React.PureComponent<IProps, IState> {
public render() {
let seconds = this.state.seconds;
if (this.state.playbackPhase === PlaybackState.Stopped) {
seconds = this.state.durationSeconds;
if (Number.isFinite(this.props.defaultDisplaySeconds)) {
seconds = this.props.defaultDisplaySeconds;
} else {
seconds = this.state.durationSeconds;
}
}
return <Clock seconds={seconds} />;
}

View file

@ -33,7 +33,7 @@ interface IState {
/**
* A waveform which shows the waveform of a previously recorded recording
*/
@replaceableComponent("views.voice_messages.PlaybackWaveform")
@replaceableComponent("views.audio_messages.PlaybackWaveform")
export default class PlaybackWaveform extends React.PureComponent<IProps, IState> {
public constructor(props) {
super(props);

View file

@ -17,20 +17,25 @@ 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 {
playbackPhase: PlaybackState;
}
@replaceableComponent("views.audio_messages.RecordingPlayback")
export default class RecordingPlayback extends React.PureComponent<IProps, IState> {
constructor(props: IProps) {
super(props);
@ -48,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_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

@ -0,0 +1,112 @@
/*
Copyright 2021 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
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 { Playback, PlaybackState } from "../../../voice/Playback";
import React, { ChangeEvent, CSSProperties, ReactNode } from "react";
import { replaceableComponent } from "../../../utils/replaceableComponent";
import { MarkedExecution } from "../../../utils/MarkedExecution";
import { percentageOf } from "../../../utils/numbers";
interface IProps {
// Playback instance to render. Cannot change during component lifecycle: create
// an all-new component instead.
playback: Playback;
// Tab index for the underlying component. Useful if the seek bar is in a managed state.
// Defaults to zero.
tabIndex?: number;
playbackPhase: PlaybackState;
}
interface IState {
percentage: number;
}
interface ISeekCSS extends CSSProperties {
'--fillTo': number;
}
const ARROW_SKIP_SECONDS = 5; // arbitrary
@replaceableComponent("views.audio_messages.SeekBar")
export default class SeekBar extends React.PureComponent<IProps, IState> {
// We use an animation frame request to avoid overly spamming prop updates, even if we aren't
// really using anything demanding on the CSS front.
private animationFrameFn = new MarkedExecution(
() => this.doUpdate(),
() => requestAnimationFrame(() => this.animationFrameFn.trigger()));
public static defaultProps = {
tabIndex: 0,
};
constructor(props: IProps) {
super(props);
this.state = {
percentage: 0,
};
// We don't need to de-register: the class handles this for us internally
this.props.playback.clockInfo.liveData.onUpdate(() => this.animationFrameFn.mark());
}
private doUpdate() {
this.setState({
percentage: percentageOf(
this.props.playback.clockInfo.timeSeconds,
0,
this.props.playback.clockInfo.durationSeconds),
});
}
public left() {
// noinspection JSIgnoredPromiseFromCall
this.props.playback.skipTo(this.props.playback.clockInfo.timeSeconds - ARROW_SKIP_SECONDS);
}
public right() {
// noinspection JSIgnoredPromiseFromCall
this.props.playback.skipTo(this.props.playback.clockInfo.timeSeconds + ARROW_SKIP_SECONDS);
}
private onChange = (ev: ChangeEvent<HTMLInputElement>) => {
// Thankfully, onChange is only called when the user changes the value, not when we
// change the value on the component. We can use this as a reliable "skip to X" function.
//
// noinspection JSIgnoredPromiseFromCall
this.props.playback.skipTo(Number(ev.target.value) * this.props.playback.clockInfo.durationSeconds);
};
public render(): ReactNode {
// We use a range input to avoid having to re-invent accessibility handling on
// a custom set of divs.
return <input
type="range"
className='mx_SeekBar'
tabIndex={this.props.tabIndex}
onChange={this.onChange}
min={0}
max={1}
value={this.state.percentage}
step={0.001}
style={{ '--fillTo': this.state.percentage } as ISeekCSS}
disabled={this.props.playbackPhase === PlaybackState.Decoding}
/>;
}
}

View file

@ -17,8 +17,13 @@ limitations under the License.
import React from "react";
import { replaceableComponent } from "../../../utils/replaceableComponent";
import classNames from "classnames";
import { CSSProperties } from "react";
export interface IProps {
interface WaveformCSSProperties extends CSSProperties {
'--barHeight': number;
}
interface IProps {
relHeights: number[]; // relative heights (0-1)
progress: number; // percent complete, 0-1, default 100%
}
@ -34,14 +39,7 @@ interface IState {
* For CSS purposes, a mx_Waveform_bar_100pct class is added when the bar should be
* "filled", as a demonstration of the progress property.
*/
import { CSSProperties } from "react";
export interface WaveformCSSProperties extends CSSProperties {
'--barHeight': number;
}
@replaceableComponent("views.voice_messages.Waveform")
@replaceableComponent("views.audio_messages.Waveform")
export default class Waveform extends React.PureComponent<IProps, IState> {
public static defaultProps = {
progress: 1,
@ -49,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({
@ -59,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

@ -18,7 +18,6 @@ import React, { ChangeEvent, createRef, FormEvent, MouseEvent } from 'react';
import classNames from 'classnames';
import { MatrixClient } from "matrix-js-sdk/src/client";
import * as sdk from '../../../index';
import { _t } from '../../../languageHandler';
import SettingsStore from "../../../settings/SettingsStore";
import AccessibleButton from "../elements/AccessibleButton";
@ -26,6 +25,8 @@ import Spinner from "../elements/Spinner";
import CountlyAnalytics from "../../../CountlyAnalytics";
import { replaceableComponent } from "../../../utils/replaceableComponent";
import { LocalisedPolicy, Policies } from '../../../Terms';
import Field from '../elements/Field';
import CaptchaForm from "./CaptchaForm";
/* This file contains a collection of components which are used by the
* InteractiveAuth to prompt the user to enter the information needed
@ -40,7 +41,7 @@ import { LocalisedPolicy, Policies } from '../../../Terms';
* 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
@ -53,8 +54,8 @@ import { LocalisedPolicy, Policies } from '../../../Terms';
* 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.
@ -164,8 +165,7 @@ export class PasswordAuthEntry extends React.Component<IAuthEntryProps, IPasswor
let submitButtonOrSpinner;
if (this.props.busy) {
const Loader = sdk.getComponent("elements.Spinner");
submitButtonOrSpinner = <Loader />;
submitButtonOrSpinner = <Spinner />;
} else {
submitButtonOrSpinner = (
<input type="submit"
@ -185,8 +185,6 @@ export class PasswordAuthEntry extends React.Component<IAuthEntryProps, IPasswor
);
}
const Field = sdk.getComponent('elements.Field');
return (
<div>
<p>{ _t("Confirm your identity by entering your account password below.") }</p>
@ -236,13 +234,11 @@ export class RecaptchaAuthEntry extends React.Component<IRecaptchaAuthEntryProps
render() {
if (this.props.busy) {
const Loader = sdk.getComponent("elements.Spinner");
return <Loader />;
return <Spinner />;
}
let errorText = this.props.errorText;
const CaptchaForm = sdk.getComponent("views.auth.CaptchaForm");
let sitePublicKey;
if (!this.props.stageParams || !this.props.stageParams.public_key) {
errorText = _t(
@ -390,8 +386,7 @@ export class TermsAuthEntry extends React.Component<ITermsAuthEntryProps, ITerms
render() {
if (this.props.busy) {
const Loader = sdk.getComponent("elements.Spinner");
return <Loader />;
return <Spinner />;
}
const checkboxes = [];
@ -422,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 }
@ -590,8 +585,7 @@ export class MsisdnAuthEntry extends React.Component<IMsisdnAuthEntryProps, IMsi
render() {
if (this.state.requestingToken) {
const Loader = sdk.getComponent("elements.Spinner");
return <Loader />;
return <Spinner />;
} else {
const enableSubmit = Boolean(this.state.token);
const submitClasses = classNames({
@ -619,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")}
@ -627,7 +621,7 @@ export class MsisdnAuthEntry extends React.Component<IMsisdnAuthEntryProps, IMsi
disabled={!enableSubmit}
/>
</form>
{errorSection}
{ errorSection }
</div>
</div>
);
@ -723,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>
);
}
@ -759,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>;
}
@ -831,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

@ -52,8 +52,8 @@ interface IProps {
interface IState {
fieldValid: Partial<Record<LoginField, boolean>>;
loginType: LoginField.Email | LoginField.MatrixId | LoginField.Phone,
password: "",
loginType: LoginField.Email | LoginField.MatrixId | LoginField.Phone;
password: "";
}
enum LoginField {
@ -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

@ -17,7 +17,6 @@ limitations under the License.
import React from 'react';
import * as sdk from '../../../index';
import * as Email from '../../../email';
import { looksValid as phoneNumberLooksValid } from '../../../phonenumber';
import Modal from '../../../Modal';
@ -31,6 +30,7 @@ import CountlyAnalytics from "../../../CountlyAnalytics";
import Field from '../elements/Field';
import RegistrationEmailPromptDialog from '../dialogs/RegistrationEmailPromptDialog';
import { replaceableComponent } from "../../../utils/replaceableComponent";
import CountryDropdown from "./CountryDropdown";
enum RegistrationField {
Email = "field_email",
@ -471,7 +471,6 @@ export default class RegistrationForm extends React.PureComponent<IProps, IState
if (!this.showPhoneNumber()) {
return null;
}
const CountryDropdown = sdk.getComponent('views.auth.CountryDropdown');
const phoneLabel = this.authStepIsRequired('m.login.msisdn') ?
_t("Phone") :
_t("Phone (optional)");
@ -538,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

@ -30,13 +30,14 @@ import { _t } from "../../../languageHandler";
import TextWithTooltip from "../elements/TextWithTooltip";
import DMRoomMap from "../../../utils/DMRoomMap";
import { replaceableComponent } from "../../../utils/replaceableComponent";
import { IOOBData } from "../../../stores/ThreepidInviteStore";
interface IProps {
room: Room;
avatarSize: number;
displayBadge?: boolean;
forceCount?: boolean;
oobData?: object;
oobData?: IOOBData;
viewAvatarOnClick?: boolean;
}
@ -204,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,16 +22,17 @@ 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';
interface IProps extends Omit<ComponentProps<typeof BaseAvatar>, "name" | "idName" | "url" | "onClick"> {
// Room may be left unset here, but if it is,
// oobData.avatarUrl should be set (else there
// would be nowhere to get the avatar from)
room?: Room;
// TODO: type when js-sdk has types
oobData?: any;
oobData?: IOOBData;
width?: number;
height?: number;
resizeMethod?: ResizeMethod;
@ -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

@ -1,6 +1,6 @@
/*
Copyright 2019 Michael Telatynski <7t3chguy@gmail.com>
Copyright 2015, 2016, 2018, 2019, 2021 The Matrix.org Foundation C.I.C.
Copyright 2015 - 2021 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -16,12 +16,11 @@ limitations under the License.
*/
import React from 'react';
import PropTypes from 'prop-types';
import { EventStatus } from 'matrix-js-sdk/src/models/event';
import { EventStatus, MatrixEvent } from 'matrix-js-sdk/src/models/event';
import { EventType, RelationType } from "matrix-js-sdk/src/@types/event";
import { MatrixClientPeg } from '../../../MatrixClientPeg';
import dis from '../../../dispatcher/dispatcher';
import * as sdk from '../../../index';
import { _t } from '../../../languageHandler';
import Modal from '../../../Modal';
import Resend from '../../../Resend';
@ -29,53 +28,69 @@ import SettingsStore from '../../../settings/SettingsStore';
import { isUrlPermitted } from '../../../HtmlUtils';
import { isContentActionable } from '../../../utils/EventUtils';
import IconizedContextMenu, { IconizedContextMenuOption, IconizedContextMenuOptionList } from './IconizedContextMenu';
import { EventType } from "matrix-js-sdk/src/@types/event";
import { replaceableComponent } from "../../../utils/replaceableComponent";
import { ReadPinsEventId } from "../right_panel/PinnedMessagesCard";
import ForwardDialog from "../dialogs/ForwardDialog";
import { Action } from "../../../dispatcher/actions";
import ReportEventDialog from '../dialogs/ReportEventDialog';
import ViewSource from '../../structures/ViewSource';
import ConfirmRedactDialog from '../dialogs/ConfirmRedactDialog';
import ErrorDialog from '../dialogs/ErrorDialog';
import ShareDialog from '../dialogs/ShareDialog';
import { RoomPermalinkCreator } from "../../../utils/permalinks/Permalinks";
export function canCancel(eventStatus) {
export function canCancel(eventStatus: EventStatus): boolean {
return eventStatus === EventStatus.QUEUED || eventStatus === EventStatus.NOT_SENT;
}
export interface IEventTileOps {
isWidgetHidden(): boolean;
unhideWidget(): void;
}
export interface IOperableEventTile {
getEventTileOps(): IEventTileOps;
}
interface IProps {
/* the MatrixEvent associated with the context menu */
mxEvent: MatrixEvent;
/* an optional EventTileOps implementation that can be used to unhide preview widgets */
eventTileOps?: IEventTileOps;
permalinkCreator?: RoomPermalinkCreator;
/* an optional function to be called when the user clicks collapse thread, if not provided hide button */
collapseReplyThread?(): void;
/* callback called when the menu is dismissed */
onFinished(): void;
/* if the menu is inside a dialog, we sometimes need to close that dialog after click (forwarding) */
onCloseDialog?(): void;
}
interface IState {
canRedact: boolean;
canPin: boolean;
}
@replaceableComponent("views.context_menus.MessageContextMenu")
export default class MessageContextMenu extends React.Component {
static propTypes = {
/* the MatrixEvent associated with the context menu */
mxEvent: PropTypes.object.isRequired,
/* an optional EventTileOps implementation that can be used to unhide preview widgets */
eventTileOps: PropTypes.object,
/* an optional function to be called when the user clicks collapse thread, if not provided hide button */
collapseReplyThread: PropTypes.func,
/* callback called when the menu is dismissed */
onFinished: PropTypes.func,
/* if the menu is inside a dialog, we sometimes need to close that dialog after click (forwarding) */
onCloseDialog: PropTypes.func,
};
export default class MessageContextMenu extends React.Component<IProps, IState> {
state = {
canRedact: false,
canPin: false,
};
componentDidMount() {
MatrixClientPeg.get().on('RoomMember.powerLevel', this._checkPermissions);
this._checkPermissions();
MatrixClientPeg.get().on('RoomMember.powerLevel', this.checkPermissions);
this.checkPermissions();
}
componentWillUnmount() {
const cli = MatrixClientPeg.get();
if (cli) {
cli.removeListener('RoomMember.powerLevel', this._checkPermissions);
cli.removeListener('RoomMember.powerLevel', this.checkPermissions);
}
}
_checkPermissions = () => {
private checkPermissions = (): void => {
const cli = MatrixClientPeg.get();
const room = cli.getRoom(this.props.mxEvent.getRoomId());
@ -93,7 +108,7 @@ export default class MessageContextMenu extends React.Component {
this.setState({ canRedact, canPin });
};
_isPinned() {
private isPinned(): boolean {
const room = MatrixClientPeg.get().getRoom(this.props.mxEvent.getRoomId());
const pinnedEvent = room.currentState.getStateEvents(EventType.RoomPinnedEvents, '');
if (!pinnedEvent) return false;
@ -101,38 +116,35 @@ export default class MessageContextMenu extends React.Component {
return content.pinned && Array.isArray(content.pinned) && content.pinned.includes(this.props.mxEvent.getId());
}
onResendReactionsClick = () => {
for (const reaction of this._getUnsentReactions()) {
private onResendReactionsClick = (): void => {
for (const reaction of this.getUnsentReactions()) {
Resend.resend(reaction);
}
this.closeMenu();
};
onReportEventClick = () => {
const ReportEventDialog = sdk.getComponent("dialogs.ReportEventDialog");
private onReportEventClick = (): void => {
Modal.createTrackedDialog('Report Event', '', ReportEventDialog, {
mxEvent: this.props.mxEvent,
}, 'mx_Dialog_reportEvent');
this.closeMenu();
};
onViewSourceClick = () => {
const ViewSource = sdk.getComponent('structures.ViewSource');
private onViewSourceClick = (): void => {
Modal.createTrackedDialog('View Event Source', '', ViewSource, {
mxEvent: this.props.mxEvent,
}, 'mx_Dialog_viewsource');
this.closeMenu();
};
onRedactClick = () => {
const ConfirmRedactDialog = sdk.getComponent("dialogs.ConfirmRedactDialog");
private onRedactClick = (): void => {
Modal.createTrackedDialog('Confirm Redact Dialog', '', ConfirmRedactDialog, {
onFinished: async (proceed, reason) => {
onFinished: async (proceed: boolean, reason?: string) => {
if (!proceed) return;
const cli = MatrixClientPeg.get();
try {
if (this.props.onCloseDialog) this.props.onCloseDialog();
this.props.onCloseDialog?.();
await cli.redactEvent(
this.props.mxEvent.getRoomId(),
this.props.mxEvent.getId(),
@ -145,7 +157,6 @@ export default class MessageContextMenu extends React.Component {
// (e.g. no errcode or statusCode) as in that case the redactions end up in the
// detached queue and we show the room status bar to allow retry
if (typeof code !== "undefined") {
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
// display error message stating you couldn't delete this.
Modal.createTrackedDialog('You cannot delete this message', '', ErrorDialog, {
title: _t('Error'),
@ -158,7 +169,7 @@ export default class MessageContextMenu extends React.Component {
this.closeMenu();
};
onForwardClick = () => {
private onForwardClick = (): void => {
Modal.createTrackedDialog('Forward Message', '', ForwardDialog, {
matrixClient: MatrixClientPeg.get(),
event: this.props.mxEvent,
@ -167,12 +178,12 @@ export default class MessageContextMenu extends React.Component {
this.closeMenu();
};
onPinClick = () => {
private onPinClick = (): void => {
const cli = MatrixClientPeg.get();
const room = cli.getRoom(this.props.mxEvent.getRoomId());
const eventId = this.props.mxEvent.getId();
const pinnedIds = room?.currentState?.getStateEvents(EventType.RoomPinnedEvents, "")?.pinned || [];
const pinnedIds = room?.currentState?.getStateEvents(EventType.RoomPinnedEvents, "")?.getContent().pinned || [];
if (pinnedIds.includes(eventId)) {
pinnedIds.splice(pinnedIds.indexOf(eventId), 1);
} else {
@ -188,18 +199,16 @@ export default class MessageContextMenu extends React.Component {
this.closeMenu();
};
closeMenu = () => {
if (this.props.onFinished) this.props.onFinished();
private closeMenu = (): void => {
this.props.onFinished();
};
onUnhidePreviewClick = () => {
if (this.props.eventTileOps) {
this.props.eventTileOps.unhideWidget();
}
private onUnhidePreviewClick = (): void => {
this.props.eventTileOps?.unhideWidget();
this.closeMenu();
};
onQuoteClick = () => {
private onQuoteClick = (): void => {
dis.dispatch({
action: Action.ComposerInsert,
event: this.props.mxEvent,
@ -207,9 +216,8 @@ export default class MessageContextMenu extends React.Component {
this.closeMenu();
};
onPermalinkClick = (e) => {
private onPermalinkClick = (e: React.MouseEvent): void => {
e.preventDefault();
const ShareDialog = sdk.getComponent("dialogs.ShareDialog");
Modal.createTrackedDialog('share room message dialog', '', ShareDialog, {
target: this.props.mxEvent,
permalinkCreator: this.props.permalinkCreator,
@ -217,30 +225,27 @@ export default class MessageContextMenu extends React.Component {
this.closeMenu();
};
onCollapseReplyThreadClick = () => {
private onCollapseReplyThreadClick = (): void => {
this.props.collapseReplyThread();
this.closeMenu();
};
_getReactions(filter) {
private getReactions(filter: (e: MatrixEvent) => boolean): MatrixEvent[] {
const cli = MatrixClientPeg.get();
const room = cli.getRoom(this.props.mxEvent.getRoomId());
const eventId = this.props.mxEvent.getId();
return room.getPendingEvents().filter(e => {
const relation = e.getRelation();
return relation &&
relation.rel_type === "m.annotation" &&
relation.event_id === eventId &&
filter(e);
return relation?.rel_type === RelationType.Annotation && relation.event_id === eventId && filter(e);
});
}
_getPendingReactions() {
return this._getReactions(e => canCancel(e.status));
private getPendingReactions(): MatrixEvent[] {
return this.getReactions(e => canCancel(e.status));
}
_getUnsentReactions() {
return this._getReactions(e => e.status === EventStatus.NOT_SENT);
private getUnsentReactions(): MatrixEvent[] {
return this.getReactions(e => e.status === EventStatus.NOT_SENT);
}
render() {
@ -248,16 +253,17 @@ export default class MessageContextMenu extends React.Component {
const me = cli.getUserId();
const mxEvent = this.props.mxEvent;
const eventStatus = mxEvent.status;
const unsentReactionsCount = this._getUnsentReactions().length;
let resendReactionsButton;
let redactButton;
let forwardButton;
let pinButton;
let unhidePreviewButton;
let externalURLButton;
let quoteButton;
let collapseReplyThread;
let redactItemList;
const unsentReactionsCount = this.getUnsentReactions().length;
let resendReactionsButton: JSX.Element;
let redactButton: JSX.Element;
let forwardButton: JSX.Element;
let pinButton: JSX.Element;
let unhidePreviewButton: JSX.Element;
let externalURLButton: JSX.Element;
let quoteButton: JSX.Element;
let collapseReplyThread: JSX.Element;
let redactItemList: JSX.Element;
// status is SENT before remote-echo, null after
const isSent = !eventStatus || eventStatus === EventStatus.SENT;
@ -266,7 +272,7 @@ export default class MessageContextMenu extends React.Component {
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}
/>
);
@ -296,7 +302,7 @@ export default class MessageContextMenu extends React.Component {
pinButton = (
<IconizedContextMenuOption
iconClassName="mx_MessageContextMenu_iconPin"
label={ this._isPinned() ? _t('Unpin') : _t('Pin') }
label={this.isPinned() ? _t('Unpin') : _t('Pin')}
onClick={this.onPinClick}
/>
);
@ -327,16 +333,20 @@ export default class MessageContextMenu extends React.Component {
if (this.props.permalinkCreator) {
permalink = this.props.permalinkCreator.forEvent(this.props.mxEvent.getId());
}
// XXX: if we use room ID, we should also include a server where the event can be found (other than in the domain of the event ID)
const permalinkButton = (
<IconizedContextMenuOption
iconClassName="mx_MessageContextMenu_iconPermalink"
onClick={this.onPermalinkClick}
label= {_t('Share')}
label={_t('Share')}
element="a"
href={permalink}
target="_blank"
rel="noreferrer noopener"
{
// XXX: Typescript signature for AccessibleButton doesn't work properly for non-inputs like `a`
...{
href: permalink,
target: "_blank",
rel: "noreferrer noopener",
}
}
/>
);
@ -351,18 +361,23 @@ export default class MessageContextMenu extends React.Component {
}
// Bridges can provide a 'external_url' to link back to the source.
if (typeof (mxEvent.event.content.external_url) === "string" &&
isUrlPermitted(mxEvent.event.content.external_url)
if (typeof (mxEvent.getContent().external_url) === "string" &&
isUrlPermitted(mxEvent.getContent().external_url)
) {
externalURLButton = (
<IconizedContextMenuOption
iconClassName="mx_MessageContextMenu_iconLink"
onClick={this.closeMenu}
label={ _t('Source URL') }
label={_t('Source URL')}
element="a"
target="_blank"
rel="noreferrer noopener"
href={mxEvent.event.content.external_url}
{
// XXX: Typescript signature for AccessibleButton doesn't work properly for non-inputs like `a`
...{
target: "_blank",
rel: "noreferrer noopener",
href: mxEvent.getContent().external_url,
}
}
/>
);
}
@ -377,7 +392,7 @@ export default class MessageContextMenu extends React.Component {
);
}
let reportEventButton;
let reportEventButton: JSX.Element;
if (mxEvent.getSender() !== me) {
reportEventButton = (
<IconizedContextMenuOption

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

@ -18,6 +18,7 @@ import React, { ReactNode, useContext, useMemo, useState } from "react";
import classNames from "classnames";
import { Room } from "matrix-js-sdk/src/models/room";
import { MatrixClient } from "matrix-js-sdk/src/client";
import { sleep } from "matrix-js-sdk/src/utils";
import { _t } from '../../../languageHandler';
import { IDialogProps } from "./IDialogProps";
@ -29,7 +30,6 @@ import RoomAvatar from "../avatars/RoomAvatar";
import { getDisplayAliasForRoom } from "../../../Rooms";
import AccessibleButton from "../elements/AccessibleButton";
import AutoHideScrollbar from "../../structures/AutoHideScrollbar";
import { sleep } from "../../../utils/promise";
import DMRoomMap from "../../../utils/DMRoomMap";
import { calculateRoomVia } from "../../../utils/permalinks/Permalinks";
import StyledCheckbox from "../elements/StyledCheckbox";
@ -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

@ -19,6 +19,7 @@ limitations under the License.
import React, { createRef } from 'react';
import PropTypes from 'prop-types';
import { sleep } from "matrix-js-sdk/src/utils";
import { _t, _td } from '../../../languageHandler';
import * as sdk from '../../../index';
@ -30,7 +31,6 @@ import * as Email from '../../../email';
import IdentityAuthClient from '../../../IdentityAuthClient';
import { getDefaultIdentityServerUrl, useDefaultIdentityServer } from '../../../utils/IdentityServerUtils';
import { abbreviateUrl } from '../../../utils/UrlUtils';
import { sleep } from "../../../utils/promise";
import { Key } from "../../../Keyboard";
import { Action } from "../../../dispatcher/actions";
import { replaceableComponent } from "../../../utils/replaceableComponent";
@ -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

@ -15,11 +15,11 @@ limitations under the License.
*/
import React from 'react';
import * as sdk from '../../../index';
import { _t } from '../../../languageHandler';
import SettingsStore from "../../../settings/SettingsStore";
import { SettingLevel } from "../../../settings/SettingLevel";
import { replaceableComponent } from "../../../utils/replaceableComponent";
import BaseDialog from "./BaseDialog";
interface IProps {
unknownProfileUsers: Array<{
@ -50,10 +50,8 @@ export default class AskInviteAnywayDialog extends React.Component<IProps> {
};
public render() {
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
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'
@ -62,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

@ -18,13 +18,17 @@ limitations under the License.
*/
import React from 'react';
import * as sdk from '../../../index';
import SdkConfig from '../../../SdkConfig';
import Modal from '../../../Modal';
import { _t } from '../../../languageHandler';
import sendBugReport, { downloadBugReport } from '../../../rageshake/submit-rageshake';
import AccessibleButton from "../elements/AccessibleButton";
import { replaceableComponent } from "../../../utils/replaceableComponent";
import QuestionDialog from "./QuestionDialog";
import BaseDialog from "./BaseDialog";
import Field from '../elements/Field';
import Spinner from "../elements/Spinner";
import DialogButtons from "../elements/DialogButtons";
interface IProps {
onFinished: (success: boolean) => void;
@ -93,7 +97,6 @@ export default class BugReportDialog extends React.Component<IProps, IState> {
}).then(() => {
if (!this.unmounted) {
this.props.onFinished(false);
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
// N.B. first param is passed to piwik and so doesn't want i18n
Modal.createTrackedDialog('Bug report sent', '', QuestionDialog, {
title: _t('Logs sent'),
@ -160,15 +163,10 @@ export default class BugReportDialog extends React.Component<IProps, IState> {
};
public render() {
const Loader = sdk.getComponent("elements.Spinner");
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
const Field = sdk.getComponent('elements.Field');
let error = null;
if (this.state.err) {
error = <div className="error">
{this.state.err}
{ this.state.err }
</div>;
}
@ -176,8 +174,8 @@ export default class BugReportDialog extends React.Component<IProps, IState> {
if (this.state.busy) {
progress = (
<div className="progress">
<Loader />
{this.state.progress} ...
<Spinner />
{ this.state.progress } ...
</div>
);
}
@ -223,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
@ -248,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

@ -16,9 +16,10 @@ Copyright 2019 Michael Telatynski <7t3chguy@gmail.com>
*/
import React from 'react';
import * as sdk from '../../../index';
import request from 'browser-request';
import { _t } from '../../../languageHandler';
import QuestionDialog from "./QuestionDialog";
import Spinner from "../elements/Spinner";
interface IProps {
newVersion: string;
@ -58,16 +59,13 @@ 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>
);
}
public render() {
const Spinner = sdk.getComponent('views.elements.Spinner');
const QuestionDialog = sdk.getComponent('dialogs.QuestionDialog');
const logs = REPOS.map(repo => {
let content;
if (this.state[repo] == null) {
@ -81,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

@ -15,9 +15,12 @@ limitations under the License.
*/
import React from 'react';
import * as sdk from '../../../index';
import { _t } from '../../../languageHandler';
import { replaceableComponent } from "../../../utils/replaceableComponent";
import ConfirmRedactDialog from './ConfirmRedactDialog';
import ErrorDialog from './ErrorDialog';
import BaseDialog from "./BaseDialog";
import Spinner from "../elements/Spinner";
interface IProps {
redact: () => Promise<void>;
@ -73,7 +76,6 @@ export default class ConfirmAndWaitRedactDialog extends React.PureComponent<IPro
public render() {
if (this.state.isRedacting) {
if (this.state.redactionErrorCode) {
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
const code = this.state.redactionErrorCode;
return (
<ErrorDialog
@ -83,8 +85,6 @@ export default class ConfirmAndWaitRedactDialog extends React.PureComponent<IPro
/>
);
} else {
const BaseDialog = sdk.getComponent("dialogs.BaseDialog");
const Spinner = sdk.getComponent('elements.Spinner');
return (
<BaseDialog
onFinished={this.props.onFinished}
@ -95,7 +95,6 @@ export default class ConfirmAndWaitRedactDialog extends React.PureComponent<IPro
);
}
} else {
const ConfirmRedactDialog = sdk.getComponent("dialogs.ConfirmRedactDialog");
return <ConfirmRedactDialog onFinished={this.onParentFinished} />;
}
}

View file

@ -15,9 +15,9 @@ limitations under the License.
*/
import React from 'react';
import * as sdk from '../../../index';
import { _t } from '../../../languageHandler';
import { replaceableComponent } from "../../../utils/replaceableComponent";
import TextInputDialog from "./TextInputDialog";
interface IProps {
onFinished: (success: boolean) => void;
@ -29,7 +29,6 @@ interface IProps {
@replaceableComponent("views.dialogs.ConfirmRedactDialog")
export default class ConfirmRedactDialog extends React.Component<IProps> {
render() {
const TextInputDialog = sdk.getComponent('views.dialogs.TextInputDialog');
return (
<TextInputDialog onFinished={this.props.onFinished}
title={_t("Confirm Removal")}

View file

@ -17,11 +17,14 @@ limitations under the License.
import React from 'react';
import { MatrixClient } from 'matrix-js-sdk/src/client';
import { RoomMember } from "matrix-js-sdk/src/models/room-member";
import * as sdk from '../../../index';
import { _t } from '../../../languageHandler';
import { GroupMemberType } from '../../../groups';
import { replaceableComponent } from "../../../utils/replaceableComponent";
import { mediaFromMxc } from "../../../customisations/Media";
import MemberAvatar from '../avatars/MemberAvatar';
import BaseAvatar from '../avatars/BaseAvatar';
import BaseDialog from "./BaseDialog";
import DialogButtons from "../elements/DialogButtons";
interface IProps {
// matrix-js-sdk (room) member object. Supply either this or 'groupMember'
@ -29,7 +32,7 @@ interface IProps {
// group member object. Supply either this or 'member'
groupMember: GroupMemberType;
// needed if a group member is specified
matrixClient?: MatrixClient,
matrixClient?: MatrixClient;
action: string; // eg. 'Ban'
title: string; // eg. 'Ban this user?'
@ -67,11 +70,6 @@ export default class ConfirmUserActionDialog extends React.Component<IProps> {
};
public render() {
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
const MemberAvatar = sdk.getComponent("views.avatars.MemberAvatar");
const BaseAvatar = sdk.getComponent("views.avatars.BaseAvatar");
const confirmButtonClass = this.props.danger ? 'danger' : '';
let reasonBox;

View file

@ -16,8 +16,9 @@ limitations under the License.
import React from 'react';
import { _t } from "../../../languageHandler";
import * as sdk from "../../../index";
import { replaceableComponent } from "../../../utils/replaceableComponent";
import BaseDialog from "./BaseDialog";
import DialogButtons from "../elements/DialogButtons";
interface IProps {
onFinished: (success: boolean) => void;
@ -34,9 +35,6 @@ export default class ConfirmWipeDeviceDialog extends React.Component<IProps> {
};
render() {
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
return (
<BaseDialog
className='mx_ConfirmWipeDeviceDialog'
@ -46,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

@ -15,11 +15,12 @@ limitations under the License.
*/
import React from 'react';
import * as sdk from '../../../index';
import dis from '../../../dispatcher/dispatcher';
import { _t } from '../../../languageHandler';
import { MatrixClientPeg } from '../../../MatrixClientPeg';
import { replaceableComponent } from "../../../utils/replaceableComponent";
import BaseDialog from "./BaseDialog";
import Spinner from "../elements/Spinner";
interface IProps {
onFinished: (success: boolean) => void;
@ -101,14 +102,11 @@ export default class CreateGroupDialog extends React.Component<IProps, IState> {
});
};
_onCancel = () => {
private onCancel = () => {
this.props.onFinished(false);
};
render() {
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
const Spinner = sdk.getComponent('elements.Spinner');
if (this.state.creating) {
return <Spinner />;
}
@ -169,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

@ -16,21 +16,22 @@ limitations under the License.
*/
import React from 'react';
import * as sdk from '../../../index';
import dis from '../../../dispatcher/dispatcher';
import { _t } from '../../../languageHandler';
import SdkConfig from '../../../SdkConfig';
import Modal from '../../../Modal';
import BaseDialog from "./BaseDialog";
import DialogButtons from "../elements/DialogButtons";
import QuestionDialog from "./QuestionDialog";
interface IProps {
onFinished: (success: boolean) => void;
}
export default (props: IProps) => {
const CryptoStoreTooNewDialog: React.FC<IProps> = (props: IProps) => {
const brand = SdkConfig.get().brand;
const _onLogoutClicked = () => {
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
Modal.createTrackedDialog('Logout e2e db too new', '', QuestionDialog, {
title: _t("Sign out"),
description: _t(
@ -58,8 +59,6 @@ export default (props: IProps) => {
{ brand },
);
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
return (<BaseDialog className="mx_CryptoStoreTooNewDialog"
contentId='mx_Dialog_content'
title={_t("Incompatible Database")}
@ -79,3 +78,5 @@ export default (props: IProps) => {
</DialogButtons>
</BaseDialog>);
};
export default CryptoStoreTooNewDialog;

View file

@ -17,7 +17,6 @@ limitations under the License.
import React from 'react';
import * as sdk from '../../../index';
import Analytics from '../../../Analytics';
import { MatrixClientPeg } from '../../../MatrixClientPeg';
import * as Lifecycle from '../../../Lifecycle';
@ -26,6 +25,7 @@ import InteractiveAuth, { ERROR_USER_CANCELLED } from "../../structures/Interact
import { DEFAULT_PHASE, PasswordAuthEntry, SSOAuthEntry } from "../auth/InteractiveAuthEntryComponents";
import StyledCheckbox from "../elements/StyledCheckbox";
import { replaceableComponent } from "../../../utils/replaceableComponent";
import BaseDialog from "./BaseDialog";
interface IProps {
onFinished: (success: boolean) => void;
@ -165,8 +165,6 @@ export default class DeactivateAccountDialog extends React.Component<IProps, ISt
}
public render() {
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
let error = null;
if (this.state.errStr) {
error = <div className="error">
@ -174,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}
@ -232,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

@ -16,7 +16,6 @@ limitations under the License.
*/
import React, { useState, useEffect, ChangeEvent, MouseEvent } from 'react';
import * as sdk from '../../../index';
import SyntaxHighlight from '../elements/SyntaxHighlight';
import { _t } from '../../../languageHandler';
import Field from "../elements/Field";
@ -42,6 +41,8 @@ import { replaceableComponent } from "../../../utils/replaceableComponent";
import { Room } from "matrix-js-sdk/src/models/room";
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
import { SettingLevel } from '../../../settings/SettingLevel';
import BaseDialog from "./BaseDialog";
import TruncatedList from "../elements/TruncatedList";
interface IGenericEditorProps {
onBack: () => void;
@ -336,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),
@ -369,7 +370,6 @@ class FilteredList extends React.PureComponent<IFilteredListProps, IFilteredList
};
render() {
const TruncatedList = sdk.getComponent("elements.TruncatedList");
return <div>
<Field label={_t('Filter results')} autoFocus={true} size={64}
type="text" autoComplete="off" value={this.props.query} onChange={this.onQuery}
@ -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";
@ -1261,7 +1261,6 @@ export default class DevtoolsDialog extends React.PureComponent<IProps, IState>
</React.Fragment>;
}
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
return (
<BaseDialog className="mx_QuestionDialog" onFinished={this.props.onFinished} title={_t('Developer Tools')}>
{ body }

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

@ -26,9 +26,9 @@ limitations under the License.
*/
import React from 'react';
import * as sdk from '../../../index';
import { _t } from '../../../languageHandler';
import { replaceableComponent } from "../../../utils/replaceableComponent";
import BaseDialog from "./BaseDialog";
interface IProps {
onFinished: (success: boolean) => void;
@ -57,7 +57,6 @@ export default class ErrorDialog extends React.Component<IProps, IState> {
};
public render() {
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
return (
<BaseDialog
className="mx_ErrorDialog"

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

@ -18,7 +18,6 @@ import React, { createRef } from 'react';
import classNames from 'classnames';
import { _t, _td } from "../../../languageHandler";
import * as sdk from "../../../index";
import { MatrixClientPeg } from "../../../MatrixClientPeg";
import { makeRoomPermalink, makeUserPermalink } from "../../../utils/permalinks/Permalinks";
import DMRoomMap from "../../../utils/DMRoomMap";
@ -33,7 +32,6 @@ import Modal from "../../../Modal";
import { humanizeTime } from "../../../utils/humanize";
import createRoom, {
canEncryptToAllUsers,
ensureDMExists,
findDMForUser,
privateShouldBeEncrypted,
} from "../../../createRoom";
@ -65,23 +63,40 @@ 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 */
interface IRecentUser {
userId: string,
user: RoomMember,
lastActive: number,
userId: string;
user: RoomMember;
lastActive: number;
}
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.
@ -107,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;
@ -209,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>
@ -252,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;
@ -275,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;
@ -302,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>
);
@ -313,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>
);
}
@ -330,16 +345,16 @@ interface IInviteDialogProps {
// The kind of invite being performed. Assumed to be KIND_DM if
// not provided.
kind: string,
kind: string;
// The room ID this dialog is for. Only required for KIND_INVITE.
roomId: string,
roomId: string;
// The call to transfer. Only required for KIND_CALL_TRANSFER.
call: MatrixCall,
call: MatrixCall;
// Initial value to populate the filter with
initialText: string,
initialText: string;
}
interface IInviteDialogState {
@ -354,10 +369,12 @@ 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,
errorText: string,
busy: boolean;
errorText: string;
}
@replaceableComponent("views.dialogs.InviteDialog")
@ -368,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;
@ -405,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,
@ -766,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) => {
@ -825,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) {
@ -960,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
}
@ -1046,7 +1060,6 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
if (this.unmounted) return;
if (failed.length > 0) {
const QuestionDialog = sdk.getComponent('dialogs.QuestionDialog');
Modal.createTrackedDialog('Invite Paste Fail', '', QuestionDialog, {
title: _t('Failed to find the following users'),
description: _t(
@ -1139,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>
);
}
@ -1158,12 +1171,11 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
const toRender = sourceMembers.slice(0, showNum);
const hasMore = toRender.length < sourceMembers.length;
const AccessibleButton = sdk.getComponent("elements.AccessibleButton");
let showMore = null;
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);
@ -1269,10 +1309,6 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
};
render() {
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
const AccessibleButton = sdk.getComponent("elements.AccessibleButton");
const Spinner = sdk.getComponent("elements.Spinner");
let spinner = null;
if (this.state.busy) {
spinner = <Spinner w={20} h={20} />;
@ -1282,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) {
@ -1299,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>
);
} },
);
@ -1309,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>
);
} },
);
@ -1327,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) => {
@ -1335,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");
@ -1368,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"),
@ -1398,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");
@ -1419,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}
@ -1449,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

@ -1,121 +0,0 @@
/*
Copyright 2020 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import PropTypes from 'prop-types';
import { _t } from '../../../languageHandler';
import Modal from '../../../Modal';
import { replaceableComponent } from '../../../utils/replaceableComponent';
import VerificationRequestDialog from './VerificationRequestDialog';
import BaseDialog from './BaseDialog';
import DialogButtons from '../elements/DialogButtons';
import { MatrixClientPeg } from "../../../MatrixClientPeg";
import * as sdk from '../../../index';
@replaceableComponent("views.dialogs.NewSessionReviewDialog")
export default class NewSessionReviewDialog extends React.PureComponent {
static propTypes = {
userId: PropTypes.string.isRequired,
device: PropTypes.object.isRequired,
onFinished: PropTypes.func.isRequired,
}
onCancelClick = () => {
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createTrackedDialog("Verification failed", "insecure", ErrorDialog, {
headerImage: require("../../../../res/img/e2e/warning.svg"),
title: _t("Your account is not secure"),
description: <div>
{_t("One of the following may be compromised:")}
<ul>
<li>{_t("Your password")}</li>
<li>{_t("Your homeserver")}</li>
<li>{_t("This session, or the other session")}</li>
<li>{_t("The internet connection either session is using")}</li>
</ul>
<div>
{_t("We recommend you change your password and Security Key in Settings immediately")}
</div>
</div>,
onFinished: () => this.props.onFinished(false),
});
}
onContinueClick = () => {
const { userId, device } = this.props;
const cli = MatrixClientPeg.get();
const requestPromise = cli.requestVerification(
userId,
[device.deviceId],
);
this.props.onFinished(true);
Modal.createTrackedDialog('New Session Verification', 'Starting dialog', VerificationRequestDialog, {
verificationRequestPromise: requestPromise,
member: cli.getUser(userId),
onFinished: async () => {
const request = await requestPromise;
request.cancel();
},
});
}
render() {
const { device } = this.props;
const icon = <span className="mx_NewSessionReviewDialog_headerIcon mx_E2EIcon_warning"></span>;
const titleText = _t("New session");
const title = <h2 className="mx_NewSessionReviewDialog_header">
{icon}
{titleText}
</h2>;
return (
<BaseDialog
title={title}
onFinished={this.props.onFinished}
>
<div className="mx_NewSessionReviewDialog_body">
<p>{_t(
"Use this session to verify your new one, " +
"granting it access to encrypted messages:",
)}</p>
<div className="mx_NewSessionReviewDialog_deviceInfo">
<div>
<span className="mx_NewSessionReviewDialog_deviceName">
{device.getDisplayName()}
</span> <span className="mx_NewSessionReviewDialog_deviceID">
({device.deviceId})
</span>
</div>
</div>
<p>{_t(
"If you didnt sign in to this session, " +
"your account may be compromised.",
)}</p>
<DialogButtons
cancelButton={_t("This wasn't me")}
cancelButtonClass="danger"
primaryButton={_t("Continue")}
onCancel={this.onCancelClick}
onPrimaryButtonClick={this.onContinueClick}
/>
</div>
</BaseDialog>
);
}
}

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

@ -15,7 +15,6 @@ limitations under the License.
*/
import React from 'react';
import * as sdk from '../../../index';
import { _t } from '../../../languageHandler';
import { ensureDMExists } from "../../../createRoom";
import { IDialogProps } from "./IDialogProps";
@ -26,6 +25,10 @@ import Markdown from '../../../Markdown';
import { replaceableComponent } from "../../../utils/replaceableComponent";
import SettingsStore from "../../../settings/SettingsStore";
import StyledRadioButton from "../elements/StyledRadioButton";
import BaseDialog from "./BaseDialog";
import DialogButtons from "../elements/DialogButtons";
import Field from "../elements/Field";
import Spinner from "../elements/Spinner";
interface IProps extends IDialogProps {
mxEvent: MatrixEvent;
@ -37,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 = [
@ -52,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.
@ -167,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".
@ -184,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({
@ -211,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.
@ -239,15 +242,10 @@ export default class ReportEventDialog extends React.Component<IProps, IState> {
};
render() {
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
const Loader = sdk.getComponent('elements.Spinner');
const Field = sdk.getComponent('elements.Field');
let error = null;
if (this.state.err) {
error = <div className="error">
{this.state.err}
{ this.state.err }
</div>;
}
@ -255,7 +253,7 @@ export default class ReportEventDialog extends React.Component<IProps, IState> {
if (this.state.busy) {
progress = (
<div className="progress">
<Loader />
<Spinner />
</div>
);
}
@ -276,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" +
@ -310,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;
@ -328,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"
@ -387,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")}
@ -418,7 +416,7 @@ export default class ReportEventDialog extends React.Component<IProps, IState> {
"or images.")
}
</p>
{adminMessage}
{ adminMessage }
<Field
className="mx_ReportEventDialog_reason"
element="textarea"
@ -428,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

@ -24,12 +24,12 @@ import GeneralRoomSettingsTab from "../settings/tabs/room/GeneralRoomSettingsTab
import SecurityRoomSettingsTab from "../settings/tabs/room/SecurityRoomSettingsTab";
import NotificationSettingsTab from "../settings/tabs/room/NotificationSettingsTab";
import BridgeSettingsTab from "../settings/tabs/room/BridgeSettingsTab";
import * as sdk from "../../../index";
import { MatrixClientPeg } from "../../../MatrixClientPeg";
import dis from "../../../dispatcher/dispatcher";
import SettingsStore from "../../../settings/SettingsStore";
import { UIFeature } from "../../../settings/UIFeature";
import { replaceableComponent } from "../../../utils/replaceableComponent";
import BaseDialog from "./BaseDialog";
export const ROOM_GENERAL_TAB = "ROOM_GENERAL_TAB";
export const ROOM_SECURITY_TAB = "ROOM_SECURITY_TAB";
@ -119,8 +119,6 @@ export default class RoomSettingsDialog extends React.Component<IProps> {
}
render() {
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
const roomName = MatrixClientPeg.get().getRoom(this.props.roomId).name;
return (
<BaseDialog

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

@ -22,7 +22,6 @@ import { User } from "matrix-js-sdk/src/models/user";
import { Group } from "matrix-js-sdk/src/models/group";
import { RoomMember } from "matrix-js-sdk/src/models/room-member";
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
import * as sdk from '../../../index';
import { _t } from '../../../languageHandler';
import QRCode from "../elements/QRCode";
import { RoomPermalinkCreator, makeGroupPermalink, makeUserPermalink } from "../../../utils/permalinks/Permalinks";
@ -35,6 +34,8 @@ import { IDialogProps } from "./IDialogProps";
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";
const socials = [
{
@ -119,7 +120,6 @@ export default class ShareDialog extends React.PureComponent<IProps, IState> {
const successful = await copyPlaintext(this.getUrl());
const buttonRect = target.getBoundingClientRect();
const GenericTextContextMenu = sdk.getComponent('context_menus.GenericTextContextMenu');
const { close } = ContextMenu.createMenu(GenericTextContextMenu, {
...toRightOf(buttonRect, 2),
message: successful ? _t('Copied!') : _t('Failed to copy'),
@ -230,7 +230,6 @@ export default class ShareDialog extends React.PureComponent<IProps, IState> {
</>;
}
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
return <BaseDialog
title={title}
className='mx_ShareDialog'

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}

View file

@ -48,7 +48,7 @@ export default class StorageEvictedDialog extends React.Component {
"To help us prevent this in future, please <a>send us logs</a>.",
{},
{
a: text => <a href="#" onClick={this._sendBugReport}>{text}</a>,
a: text => <a href="#" onClick={this._sendBugReport}>{ text }</a>,
},
);
}
@ -60,15 +60,15 @@ export default class StorageEvictedDialog extends React.Component {
hasCancel={false}
>
<div className="mx_Dialog_content" id='mx_Dialog_content'>
<p>{_t(
<p>{ _t(
"Some session data, including encrypted message keys, is " +
"missing. Sign out and sign in to fix this, restoring keys " +
"from backup.",
)}</p>
<p>{_t(
) }</p>
<p>{ _t(
"Your browser likely removed this data when running low on " +
"disk space.",
)} {logRequest}</p>
) } { logRequest }</p>
</div>
<DialogButtons primaryButton={_t("Sign out")}
onPrimaryButtonClick={this._onSignOutClick}

View file

@ -134,7 +134,7 @@ export default class TabbedIntegrationManagerDialog extends React.Component {
key={`tab_${i}`}
disabled={this.state.busy}
>
{m.name}
{ m.name }
</AccessibleButton>
);
});
@ -163,10 +163,10 @@ export default class TabbedIntegrationManagerDialog extends React.Component {
return (
<div className='mx_TabbedIntegrationManagerDialog_container'>
<div className='mx_TabbedIntegrationManagerDialog_tabs'>
{this._renderTabs()}
{ this._renderTabs() }
</div>
<div className='mx_TabbedIntegrationManagerDialog_currentManager'>
{this._renderTab()}
{ this._renderTab() }
</div>
</div>
);

View file

@ -16,11 +16,12 @@ limitations under the License.
import url from 'url';
import React from 'react';
import * as sdk from '../../../index';
import { _t, pickBestLanguage } from '../../../languageHandler';
import { replaceableComponent } from "../../../utils/replaceableComponent";
import { SERVICE_TYPES } from "matrix-js-sdk/src/service-types";
import DialogButtons from "../elements/DialogButtons";
import BaseDialog from "./BaseDialog";
interface ITermsCheckboxProps {
onChange: (url: string, checked: boolean) => void;
@ -46,19 +47,19 @@ interface ITermsDialogProps {
* Array of [Service, policies] pairs, where policies is the response from the
* /terms endpoint for that service
*/
policiesAndServicePairs: any[],
policiesAndServicePairs: any[];
/**
* urls that the user has already agreed to
*/
agreedUrls?: string[],
agreedUrls?: string[];
/**
* Called with:
* * success {bool} True if the user accepted any douments, false if cancelled
* * agreedUrls {string[]} List of agreed URLs
*/
onFinished: (success: boolean, agreedUrls?: string[]) => void,
onFinished: (success: boolean, agreedUrls?: string[]) => void;
}
interface IState {
@ -89,9 +90,9 @@ export default class TermsDialog extends React.PureComponent<ITermsDialogProps,
private nameForServiceType(serviceType: SERVICE_TYPES, host: string): JSX.Element {
switch (serviceType) {
case SERVICE_TYPES.IS:
return <div>{_t("Identity Server")}<br />({host})</div>;
return <div>{ _t("Identity server") }<br />({ host })</div>;
case SERVICE_TYPES.IM:
return <div>{_t("Integration Manager")}<br />({host})</div>;
return <div>{ _t("Integration manager") }<br />({ host })</div>;
}
}
@ -99,13 +100,13 @@ export default class TermsDialog extends React.PureComponent<ITermsDialogProps,
switch (serviceType) {
case SERVICE_TYPES.IS:
return <div>
{_t("Find others by phone or email")}
{ _t("Find others by phone or email") }
<br />
{_t("Be found by phone or email")}
{ _t("Be found by phone or email") }
</div>;
case SERVICE_TYPES.IM:
return <div>
{_t("Use bots, bridges, widgets and sticker packs")}
{ _t("Use bots, bridges, widgets and sticker packs") }
</div>;
}
}
@ -117,9 +118,6 @@ export default class TermsDialog extends React.PureComponent<ITermsDialogProps,
};
public render() {
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
const rows = [];
for (const policiesAndService of this.props.policiesAndServicePairs) {
const parsedBaseUrl = url.parse(policiesAndService.service.baseUrl);
@ -138,10 +136,10 @@ export default class TermsDialog extends React.PureComponent<ITermsDialogProps,
}
rows.push(<tr key={termDoc[termsLang].url}>
<td className="mx_TermsDialog_service">{serviceName}</td>
<td className="mx_TermsDialog_summary">{summary}</td>
<td className="mx_TermsDialog_service">{ serviceName }</td>
<td className="mx_TermsDialog_summary">{ summary }</td>
<td>
{termDoc[termsLang].name}
{ termDoc[termsLang].name }
<a rel="noreferrer noopener" target="_blank" href={termDoc[termsLang].url}>
<span className="mx_TermsDialog_link" />
</a>
@ -188,16 +186,16 @@ export default class TermsDialog extends React.PureComponent<ITermsDialogProps,
hasCancel={false}
>
<div id='mx_Dialog_content'>
<p>{_t("To continue you need to accept the terms of this service.")}</p>
<p>{ _t("To continue you need to accept the terms of this service.") }</p>
<table className="mx_TermsDialog_termsTable"><tbody>
<tr className="mx_TermsDialog_termsTableHeader">
<th>{_t("Service")}</th>
<th>{_t("Summary")}</th>
<th>{_t("Document")}</th>
<th>{_t("Accept")}</th>
<th>{ _t("Service") }</th>
<th>{ _t("Summary") }</th>
<th>{ _t("Document") }</th>
<th>{ _t("Accept") }</th>
</tr>
{rows}
{ rows }
</tbody></table>
</div>

View file

@ -48,13 +48,13 @@ const UntrustedDeviceDialog: React.FC<IProps> = ({ device, user, onFinished }) =
className="mx_UntrustedDeviceDialog"
title={<>
<E2EIcon status="warning" size={24} hideTooltip={true} />
{ _t("Not Trusted")}
{ _t("Not Trusted") }
</>}
>
<div className="mx_Dialog_content" id='mx_Dialog_content'>
<p>{newSessionText}</p>
<p>{device.getDisplayName()} ({device.deviceId})</p>
<p>{askToVerifyText}</p>
<p>{ newSessionText }</p>
<p>{ device.getDisplayName() } ({ device.deviceId })</p>
<p>{ askToVerifyText }</p>
</div>
<div className='mx_Dialog_buttons'>
<AccessibleButton element="button" kind="secondary" onClick={() => onFinished("legacy")}>

View file

@ -16,11 +16,12 @@ limitations under the License.
*/
import React from 'react';
import * as sdk from '../../../index';
import { _t } from '../../../languageHandler';
import filesize from "filesize";
import { replaceableComponent } from "../../../utils/replaceableComponent";
import { getBlobSafeMimeType } from '../../../utils/blobs';
import BaseDialog from "./BaseDialog";
import DialogButtons from "../elements/DialogButtons";
interface IProps {
file: File;
@ -67,9 +68,6 @@ export default class UploadConfirmDialog extends React.Component<IProps> {
};
render() {
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
let title;
if (this.props.totalFiles > 1 && this.props.currentIndex !== undefined) {
title = _t(
@ -88,7 +86,7 @@ export default class UploadConfirmDialog extends React.Component<IProps> {
preview = <div className="mx_UploadConfirmDialog_previewOuter">
<div className="mx_UploadConfirmDialog_previewInner">
<div><img className="mx_UploadConfirmDialog_imagePreview" src={this.objectUrl} /></div>
<div>{this.props.file.name} ({filesize(this.props.file.size)})</div>
<div>{ this.props.file.name } ({ filesize(this.props.file.size) })</div>
</div>
</div>;
} else {
@ -97,7 +95,7 @@ export default class UploadConfirmDialog extends React.Component<IProps> {
<img className="mx_UploadConfirmDialog_fileIcon"
src={require("../../../../res/img/feather-customised/files.svg")}
/>
{this.props.file.name} ({filesize(this.props.file.size)})
{ this.props.file.name } ({ filesize(this.props.file.size) })
</div>
</div>;
}
@ -105,7 +103,7 @@ export default class UploadConfirmDialog extends React.Component<IProps> {
let uploadAllButton;
if (this.props.currentIndex + 1 < this.props.totalFiles) {
uploadAllButton = <button onClick={this.onUploadAllClick}>
{_t("Upload all")}
{ _t("Upload all") }
</button>;
}
@ -117,7 +115,7 @@ export default class UploadConfirmDialog extends React.Component<IProps> {
contentId='mx_Dialog_content'
>
<div id='mx_Dialog_content'>
{preview}
{ preview }
</div>
<DialogButtons primaryButton={_t('Upload')}
@ -125,7 +123,7 @@ export default class UploadConfirmDialog extends React.Component<IProps> {
onPrimaryButtonClick={this.onUploadClick}
focus={true}
>
{uploadAllButton}
{ uploadAllButton }
</DialogButtons>
</BaseDialog>
);

View file

@ -60,7 +60,7 @@ export default class UploadFailureDialog extends React.Component {
limit: filesize(this.props.contentMessages.getUploadLimit()),
sizeOfThisFile: filesize(this.props.badFiles[0].size),
}, {
b: sub => <b>{sub}</b>,
b: sub => <b>{ sub }</b>,
},
);
buttons = <DialogButtons primaryButton={_t('OK')}
@ -75,7 +75,7 @@ export default class UploadFailureDialog extends React.Component {
{
limit: filesize(this.props.contentMessages.getUploadLimit()),
}, {
b: sub => <b>{sub}</b>,
b: sub => <b>{ sub }</b>,
},
);
buttons = <DialogButtons primaryButton={_t('OK')}
@ -90,7 +90,7 @@ export default class UploadFailureDialog extends React.Component {
{
limit: filesize(this.props.contentMessages.getUploadLimit()),
}, {
b: sub => <b>{sub}</b>,
b: sub => <b>{ sub }</b>,
},
);
const howManyOthers = this.props.totalFiles - this.props.badFiles.length;
@ -111,11 +111,11 @@ export default class UploadFailureDialog extends React.Component {
contentId='mx_Dialog_content'
>
<div id='mx_Dialog_content'>
{message}
{preview}
{ message }
{ preview }
</div>
{buttons}
{ buttons }
</BaseDialog>
);
}

View file

@ -28,11 +28,11 @@ import PreferencesUserSettingsTab from "../settings/tabs/user/PreferencesUserSet
import VoiceUserSettingsTab from "../settings/tabs/user/VoiceUserSettingsTab";
import HelpUserSettingsTab from "../settings/tabs/user/HelpUserSettingsTab";
import FlairUserSettingsTab from "../settings/tabs/user/FlairUserSettingsTab";
import * as sdk from "../../../index";
import SdkConfig from "../../../SdkConfig";
import MjolnirUserSettingsTab from "../settings/tabs/user/MjolnirUserSettingsTab";
import { UIFeature } from "../../../settings/UIFeature";
import { replaceableComponent } from "../../../utils/replaceableComponent";
import BaseDialog from "./BaseDialog";
export enum UserTab {
General = "USER_GENERAL_TAB",
@ -81,7 +81,7 @@ export default class UserSettingsDialog extends React.Component<IProps, IState>
this.setState({ mjolnirEnabled: newValue });
};
_getTabs() {
private getTabs() {
const tabs = [];
tabs.push(new Tab(
@ -162,8 +162,6 @@ export default class UserSettingsDialog extends React.Component<IProps, IState>
}
render() {
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
return (
<BaseDialog
className='mx_UserSettingsDialog'
@ -172,7 +170,7 @@ export default class UserSettingsDialog extends React.Component<IProps, IState>
title={_t("Settings")}
>
<div className='mx_SettingsDialog_content'>
<TabbedView tabs={this._getTabs()} initialTabId={this.props.initialTabId} />
<TabbedView tabs={this.getTabs()} initialTabId={this.props.initialTabId} />
</div>
</BaseDialog>
);

View file

@ -1,5 +1,5 @@
/*
Copyright 2020 The Matrix.org Foundation C.I.C.
Copyright 2020-2021 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -15,27 +15,33 @@ limitations under the License.
*/
import React from 'react';
import PropTypes from 'prop-types';
import { MatrixClientPeg } from '../../../MatrixClientPeg';
import * as sdk from '../../../index';
import { _t } from '../../../languageHandler';
import { replaceableComponent } from "../../../utils/replaceableComponent";
import { VerificationRequest } from "matrix-js-sdk/src/crypto/verification/request/VerificationRequest";
import BaseDialog from "./BaseDialog";
import EncryptionPanel from "../right_panel/EncryptionPanel";
import { User } from 'matrix-js-sdk/src/models/user';
interface IProps {
verificationRequest: VerificationRequest;
verificationRequestPromise: Promise<VerificationRequest>;
onFinished: () => void;
member: User;
}
interface IState {
verificationRequest: VerificationRequest;
}
@replaceableComponent("views.dialogs.VerificationRequestDialog")
export default class VerificationRequestDialog extends React.Component {
static propTypes = {
verificationRequest: PropTypes.object,
verificationRequestPromise: PropTypes.object,
onFinished: PropTypes.func.isRequired,
member: PropTypes.string,
};
constructor(...args) {
super(...args);
this.state = {};
if (this.props.verificationRequest) {
this.state.verificationRequest = this.props.verificationRequest;
} else if (this.props.verificationRequestPromise) {
export default class VerificationRequestDialog extends React.Component<IProps, IState> {
constructor(props) {
super(props);
this.state = {
verificationRequest: this.props.verificationRequest,
};
if (this.props.verificationRequestPromise) {
this.props.verificationRequestPromise.then(r => {
this.setState({ verificationRequest: r });
});
@ -43,8 +49,6 @@ export default class VerificationRequestDialog extends React.Component {
}
render() {
const BaseDialog = sdk.getComponent("views.dialogs.BaseDialog");
const EncryptionPanel = sdk.getComponent("views.right_panel.EncryptionPanel");
const request = this.state.verificationRequest;
const otherUserId = request && request.otherUserId;
const member = this.props.member ||
@ -65,6 +69,7 @@ export default class VerificationRequestDialog extends React.Component {
verificationRequestPromise={this.props.verificationRequestPromise}
onClose={this.props.onFinished}
member={member}
isRoomEncrypted={false}
/>
</BaseDialog>;
}

View file

@ -105,7 +105,7 @@ export default class WidgetCapabilitiesPromptDialog extends React.PureComponent<
const checkboxRows = Object.entries(this.state.booleanStates).map(([cap, isChecked], i) => {
const text = CapabilityText.for(cap, this.props.widgetKind);
const byline = text.byline
? <span className="mx_WidgetCapabilitiesPromptDialog_byline">{text.byline}</span>
? <span className="mx_WidgetCapabilitiesPromptDialog_byline">{ text.byline }</span>
: null;
return (
@ -113,8 +113,8 @@ export default class WidgetCapabilitiesPromptDialog extends React.PureComponent<
<StyledCheckbox
checked={isChecked}
onChange={() => this.onToggle(cap)}
>{text.primary}</StyledCheckbox>
{byline}
>{ text.primary }</StyledCheckbox>
{ byline }
</div>
);
});
@ -127,8 +127,8 @@ export default class WidgetCapabilitiesPromptDialog extends React.PureComponent<
>
<form onSubmit={this.onSubmit}>
<div className="mx_Dialog_content">
<div className="text-muted">{_t("This widget would like to:")}</div>
{checkboxRows}
<div className="text-muted">{ _t("This widget would like to:") }</div>
{ checkboxRows }
<DialogButtons
primaryButton={_t("Approve")}
cancelButton={_t("Decline All")}

View file

@ -78,11 +78,11 @@ export default class WidgetOpenIDPermissionsDialog extends React.Component {
>
<div className='mx_WidgetOpenIDPermissionsDialog_content'>
<p>
{_t("The widget will verify your user ID, but won't be able to perform actions for you:")}
{ _t("The widget will verify your user ID, but won't be able to perform actions for you:") }
</p>
<p className="text-muted">
{/* cheap trim to just get the path */}
{this.props.widget.templateUrl.split("?")[0].split("#")[0]}
{ /* cheap trim to just get the path */ }
{ this.props.widget.templateUrl.split("?")[0].split("#")[0] }
</p>
</div>
<DialogButtons

View file

@ -17,7 +17,7 @@ limitations under the License.
import { debounce } from "lodash";
import classNames from 'classnames';
import React, { ChangeEvent, FormEvent } from 'react';
import { ISecretStorageKeyInfo } from "matrix-js-sdk/src";
import { ISecretStorageKeyInfo } from "matrix-js-sdk/src/crypto/api";
import * as sdk from '../../../../index';
import { MatrixClientPeg } from '../../../../MatrixClientPeg';
@ -285,11 +285,11 @@ export default class AccessSecretStorageDialog extends React.PureComponent<IProp
const resetButton = (
<div className="mx_AccessSecretStorageDialog_reset">
{_t("Forgotten or lost all recovery methods? <a>Reset all</a>", null, {
{ _t("Forgotten or lost all recovery methods? <a>Reset all</a>", null, {
a: (sub) => <a
href="" onClick={this.onResetAllClick}
className="mx_AccessSecretStorageDialog_reset_link">{sub}</a>,
})}
className="mx_AccessSecretStorageDialog_reset_link">{ sub }</a>,
}) }
</div>
);
@ -300,9 +300,9 @@ export default class AccessSecretStorageDialog extends React.PureComponent<IProp
title = _t("Reset everything");
titleClass = ['mx_AccessSecretStorageDialog_titleWithIcon mx_AccessSecretStorageDialog_resetBadge'];
content = <div>
<p>{_t("Only do this if you have no other device to complete verification with.")}</p>
<p>{_t("If you reset everything, you will restart with no trusted sessions, no trusted users, and "
+ "might not be able to see past messages.")}</p>
<p>{ _t("Only do this if you have no other device to complete verification with.") }</p>
<p>{ _t("If you reset everything, you will restart with no trusted sessions, no trusted users, and "
+ "might not be able to see past messages.") }</p>
<DialogButtons
primaryButton={_t('Reset')}
onPrimaryButtonClick={this.onConfirmResetAllClick}
@ -320,27 +320,27 @@ export default class AccessSecretStorageDialog extends React.PureComponent<IProp
let keyStatus;
if (this.state.keyMatches === false) {
keyStatus = <div className="mx_AccessSecretStorageDialog_keyStatus">
{"\uD83D\uDC4E "}{_t(
{ "\uD83D\uDC4E " }{ _t(
"Unable to access secret storage. " +
"Please verify that you entered the correct Security Phrase.",
)}
) }
</div>;
} else {
keyStatus = <div className="mx_AccessSecretStorageDialog_keyStatus" />;
}
content = <div>
<p>{_t(
<p>{ _t(
"Enter your Security Phrase or <button>Use your Security Key</button> to continue.", {},
{
button: s => <AccessibleButton className="mx_linkButton"
element="span"
onClick={this.onUseRecoveryKeyClick}
>
{s}
{ s }
</AccessibleButton>,
},
)}</p>
) }</p>
<form className="mx_AccessSecretStorageDialog_primaryContainer" onSubmit={this.onPassPhraseNext}>
<input
@ -353,7 +353,7 @@ export default class AccessSecretStorageDialog extends React.PureComponent<IProp
autoComplete="new-password"
placeholder={_t("Security Phrase")}
/>
{keyStatus}
{ keyStatus }
<DialogButtons
primaryButton={_t('Continue')}
onPrimaryButtonClick={this.onPassPhraseNext}
@ -375,11 +375,11 @@ export default class AccessSecretStorageDialog extends React.PureComponent<IProp
'mx_AccessSecretStorageDialog_recoveryKeyFeedback_invalid': this.state.recoveryKeyCorrect === false,
});
const recoveryKeyFeedback = <div className={feedbackClasses}>
{this.getKeyValidationText()}
{ this.getKeyValidationText() }
</div>;
content = <div>
<p>{_t("Use your Security Key to continue.")}</p>
<p>{ _t("Use your Security Key to continue.") }</p>
<form
className="mx_AccessSecretStorageDialog_primaryContainer"
@ -399,7 +399,7 @@ export default class AccessSecretStorageDialog extends React.PureComponent<IProp
/>
</div>
<span className="mx_AccessSecretStorageDialog_recoveryKeyEntry_entryControlSeparatorText">
{_t("or")}
{ _t("or") }
</span>
<div>
<input type="file"
@ -408,11 +408,11 @@ export default class AccessSecretStorageDialog extends React.PureComponent<IProp
onChange={this.onRecoveryKeyFileChange}
/>
<AccessibleButton kind="primary" onClick={this.onRecoveryKeyFileUploadClick}>
{_t("Upload")}
{ _t("Upload") }
</AccessibleButton>
</div>
</div>
{recoveryKeyFeedback}
{ recoveryKeyFeedback }
<DialogButtons
primaryButton={_t('Continue')}
onPrimaryButtonClick={this.onRecoveryKeyNext}
@ -435,7 +435,7 @@ export default class AccessSecretStorageDialog extends React.PureComponent<IProp
titleClass={titleClass}
>
<div>
{content}
{ content }
</div>
</BaseDialog>
);

View file

@ -16,8 +16,9 @@ limitations under the License.
import React from 'react';
import { _t } from "../../../../languageHandler";
import * as sdk from "../../../../index";
import { replaceableComponent } from "../../../../utils/replaceableComponent";
import BaseDialog from "../BaseDialog";
import DialogButtons from "../../elements/DialogButtons";
interface IProps {
onFinished: (success: boolean) => void;
@ -34,9 +35,6 @@ export default class ConfirmDestroyCrossSigningDialog extends React.Component<IP
};
render() {
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
return (
<BaseDialog
className='mx_ConfirmDestroyCrossSigningDialog'
@ -46,12 +44,12 @@ export default class ConfirmDestroyCrossSigningDialog extends React.Component<IP
>
<div className='mx_ConfirmDestroyCrossSigningDialog_content'>
<p>
{_t(
{ _t(
"Deleting cross-signing keys is permanent. " +
"Anyone you have verified with will see security alerts. " +
"You almost certainly don't want to do this, unless " +
"you've lost every device you can cross-sign from.",
)}
) }
</p>
</div>
<DialogButtons

View file

@ -175,7 +175,7 @@ export default class CreateCrossSigningDialog extends React.PureComponent<IProps
let content;
if (this.state.error) {
content = <div>
<p>{_t("Unable to set up keys")}</p>
<p>{ _t("Unable to set up keys") }</p>
<div className="mx_Dialog_buttons">
<DialogButtons primaryButton={_t('Retry')}
onPrimaryButtonClick={this.bootstrapCrossSigning}
@ -197,7 +197,7 @@ export default class CreateCrossSigningDialog extends React.PureComponent<IProps
fixedWidth={false}
>
<div>
{content}
{ content }
</div>
</BaseDialog>
);

View file

@ -288,7 +288,7 @@ export default class RestoreKeyBackupDialog extends React.PureComponent {
details = _t("Fetching keys from server...");
}
content = <div>
<div>{details}</div>
<div>{ details }</div>
<Spinner />
</div>;
} else if (this.state.loadError) {
@ -299,18 +299,18 @@ export default class RestoreKeyBackupDialog extends React.PureComponent {
if (this.state.restoreType === RESTORE_TYPE_RECOVERYKEY) {
title = _t("Security Key mismatch");
content = <div>
<p>{_t(
<p>{ _t(
"Backup could not be decrypted with this Security Key: " +
"please verify that you entered the correct Security Key.",
)}</p>
) }</p>
</div>;
} else {
title = _t("Incorrect Security Phrase");
content = <div>
<p>{_t(
<p>{ _t(
"Backup could not be decrypted with this Security Phrase: " +
"please verify that you entered the correct Security Phrase.",
)}</p>
) }</p>
</div>;
}
} else {
@ -325,14 +325,14 @@ export default class RestoreKeyBackupDialog extends React.PureComponent {
title = _t("Keys restored");
let failedToDecrypt;
if (this.state.recoverInfo.total > this.state.recoverInfo.imported) {
failedToDecrypt = <p>{_t(
failedToDecrypt = <p>{ _t(
"Failed to decrypt %(failedCount)s sessions!",
{ failedCount: this.state.recoverInfo.total - this.state.recoverInfo.imported },
)}</p>;
) }</p>;
}
content = <div>
<p>{_t("Successfully restored %(sessionCount)s keys", { sessionCount: this.state.recoverInfo.imported })}</p>
{failedToDecrypt}
<p>{ _t("Successfully restored %(sessionCount)s keys", { sessionCount: this.state.recoverInfo.imported }) }</p>
{ failedToDecrypt }
<DialogButtons primaryButton={_t('OK')}
onPrimaryButtonClick={this._onDone}
hasCancel={false}
@ -344,15 +344,15 @@ export default class RestoreKeyBackupDialog extends React.PureComponent {
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
title = _t("Enter Security Phrase");
content = <div>
<p>{_t(
<p>{ _t(
"<b>Warning</b>: you should only set up key backup " +
"from a trusted computer.", {},
{ b: sub => <b>{sub}</b> },
)}</p>
<p>{_t(
{ b: sub => <b>{ sub }</b> },
) }</p>
<p>{ _t(
"Access your secure message history and set up secure " +
"messaging by entering your Security Phrase.",
)}</p>
) }</p>
<form className="mx_RestoreKeyBackupDialog_primaryContainer">
<input type="password"
@ -370,7 +370,7 @@ export default class RestoreKeyBackupDialog extends React.PureComponent {
focus={false}
/>
</form>
{_t(
{ _t(
"If you've forgotten your Security Phrase you can "+
"<button1>use your Security Key</button1> or " +
"<button2>set up new recovery options</button2>",
@ -381,16 +381,16 @@ export default class RestoreKeyBackupDialog extends React.PureComponent {
element="span"
onClick={this._onUseRecoveryKeyClick}
>
{s}
{ s }
</AccessibleButton>,
button2: s => <AccessibleButton
className="mx_linkButton"
element="span"
onClick={this._onResetRecoveryClick}
>
{s}
{ s }
</AccessibleButton>,
})}
}) }
</div>;
} else {
title = _t("Enter Security Key");
@ -402,24 +402,24 @@ export default class RestoreKeyBackupDialog extends React.PureComponent {
keyStatus = <div className="mx_RestoreKeyBackupDialog_keyStatus"></div>;
} else if (this.state.recoveryKeyValid) {
keyStatus = <div className="mx_RestoreKeyBackupDialog_keyStatus">
{"\uD83D\uDC4D "}{_t("This looks like a valid Security Key!")}
{ "\uD83D\uDC4D " }{ _t("This looks like a valid Security Key!") }
</div>;
} else {
keyStatus = <div className="mx_RestoreKeyBackupDialog_keyStatus">
{"\uD83D\uDC4E "}{_t("Not a valid Security Key")}
{ "\uD83D\uDC4E " }{ _t("Not a valid Security Key") }
</div>;
}
content = <div>
<p>{_t(
<p>{ _t(
"<b>Warning</b>: You should only set up key backup " +
"from a trusted computer.", {},
{ b: sub => <b>{sub}</b> },
)}</p>
<p>{_t(
{ b: sub => <b>{ sub }</b> },
) }</p>
<p>{ _t(
"Access your secure message history and set up secure " +
"messaging by entering your Security Key.",
)}</p>
) }</p>
<div className="mx_RestoreKeyBackupDialog_primaryContainer">
<input className="mx_RestoreKeyBackupDialog_recoveryKeyInput"
@ -427,7 +427,7 @@ export default class RestoreKeyBackupDialog extends React.PureComponent {
value={this.state.recoveryKey}
autoFocus={true}
/>
{keyStatus}
{ keyStatus }
<DialogButtons primaryButton={_t('Next')}
onPrimaryButtonClick={this._onRecoveryKeyNext}
hasCancel={true}
@ -436,7 +436,7 @@ export default class RestoreKeyBackupDialog extends React.PureComponent {
primaryDisabled={!this.state.recoveryKeyValid}
/>
</div>
{_t(
{ _t(
"If you've forgotten your Security Key you can "+
"<button>set up new recovery options</button>",
{},
@ -445,10 +445,10 @@ export default class RestoreKeyBackupDialog extends React.PureComponent {
element="span"
onClick={this._onResetRecoveryClick}
>
{s}
{ s }
</AccessibleButton>,
},
)}
) }
</div>;
}
@ -458,7 +458,7 @@ export default class RestoreKeyBackupDialog extends React.PureComponent {
title={title}
>
<div className='mx_RestoreKeyBackupDialog_content'>
{content}
{ content }
</div>
</BaseDialog>
);

View file

@ -17,6 +17,7 @@ limitations under the License.
import React, { useEffect, useState } from "react";
import { MatrixError } from "matrix-js-sdk/src/http-api";
import { IProtocol } from "matrix-js-sdk/src/client";
import { MatrixClientPeg } from '../../../MatrixClientPeg';
import { instanceForInstanceId } from '../../../utils/DirectoryUtils';
@ -41,7 +42,8 @@ import QuestionDialog from "../dialogs/QuestionDialog";
import UIStore from "../../../stores/UIStore";
import { compare } from "../../../utils/strings";
export const ALL_ROOMS = Symbol("ALL_ROOMS");
// XXX: We would ideally use a symbol here but we can't since we save this value to localStorage
export const ALL_ROOMS = "ALL_ROOMS";
const SETTING_NAME = "room_directory_servers";
@ -82,38 +84,13 @@ const validServer = withValidation<undefined, { error?: MatrixError }>({
],
});
/* eslint-disable camelcase */
export interface IFieldType {
regexp: string;
placeholder: string;
}
export interface IInstance {
desc: string;
icon?: string;
fields: object;
network_id: string;
// XXX: this is undocumented but we rely on it.
// we inject a fake entry with a symbolic instance_id.
instance_id: string | symbol;
}
export interface IProtocol {
user_fields: string[];
location_fields: string[];
icon: string;
field_types: Record<string, IFieldType>;
instances: IInstance[];
}
/* eslint-enable camelcase */
export type Protocols = Record<string, IProtocol>;
interface IProps {
protocols: Protocols;
selectedServerName: string;
selectedInstanceId: string | symbol;
onOptionChange(server: string, instanceId?: string | symbol): void;
selectedInstanceId: string;
onOptionChange(server: string, instanceId?: string): void;
}
// This dropdown sources homeservers from three places:
@ -171,7 +148,7 @@ const NetworkDropdown = ({ onOptionChange, protocols = {}, selectedServerName, s
const protocolsList = server === hsName ? Object.values(protocols) : [];
if (protocolsList.length > 0) {
// add a fake protocol with the ALL_ROOMS symbol
// add a fake protocol with ALL_ROOMS
protocolsList.push({
instances: [{
fields: [],
@ -207,7 +184,7 @@ const NetworkDropdown = ({ onOptionChange, protocols = {}, selectedServerName, s
if (server === hsName) {
subtitle = (
<div className="mx_NetworkDropdown_server_subtitle">
{_t("Your server")}
{ _t("Your server") }
</div>
);
}
@ -261,7 +238,7 @@ const NetworkDropdown = ({ onOptionChange, protocols = {}, selectedServerName, s
label={_t("Matrix")}
className="mx_NetworkDropdown_server_network"
>
{_t("Matrix")}
{ _t("Matrix") }
</MenuItemRadio>
{ entries }
</MenuGroup>
@ -293,9 +270,9 @@ const NetworkDropdown = ({ onOptionChange, protocols = {}, selectedServerName, s
const buttonRect = handle.current.getBoundingClientRect();
content = <ContextMenu {...inPlaceOf(buttonRect)} onFinished={closeMenu}>
<div className="mx_NetworkDropdown_menu">
{options}
{ options }
<MenuItem className="mx_NetworkDropdown_server_add" label={undefined} onClick={onClick}>
{_t("Add a new server...")}
{ _t("Add a new server...") }
</MenuItem>
</div>
</ContextMenu>;
@ -318,15 +295,15 @@ const NetworkDropdown = ({ onOptionChange, protocols = {}, selectedServerName, s
isExpanded={menuDisplayed}
>
<span>
{currentValue}
{ currentValue }
</span> <span className="mx_NetworkDropdown_handle_server">
({selectedServerName})
({ selectedServerName })
</span>
</ContextMenuButton>;
}
return <div className="mx_NetworkDropdown" ref={handle}>
{content}
{ content }
</div>;
};

View file

@ -14,7 +14,7 @@
limitations under the License.
*/
import React from 'react';
import React, { ReactHTML } from 'react';
import { Key } from '../../../Keyboard';
import classnames from 'classnames';
@ -29,7 +29,7 @@ export type ButtonEvent = React.MouseEvent<Element> | React.KeyboardEvent<Elemen
*/
interface IProps extends React.InputHTMLAttributes<Element> {
inputRef?: React.Ref<Element>;
element?: string;
element?: keyof ReactHTML;
// The kind of button, similar to how Bootstrap works.
// See available classes for AccessibleButton for options.
kind?: string;
@ -122,7 +122,7 @@ export default function AccessibleButton({
}
AccessibleButton.defaultProps = {
element: 'div',
element: 'div' as keyof ReactHTML,
role: 'button',
tabIndex: 0,
};

View file

@ -94,15 +94,15 @@ export default class AppPermission extends React.Component {
const warningTooltipText = (
<div>
{_t("Any of the following data may be shared:")}
{ _t("Any of the following data may be shared:") }
<ul>
<li>{_t("Your display name")}</li>
<li>{_t("Your avatar URL")}</li>
<li>{_t("Your user ID")}</li>
<li>{_t("Your theme")}</li>
<li>{_t("%(brand)s URL", { brand })}</li>
<li>{_t("Room ID")}</li>
<li>{_t("Widget ID")}</li>
<li>{ _t("Your display name") }</li>
<li>{ _t("Your avatar URL") }</li>
<li>{ _t("Your user ID") }</li>
<li>{ _t("Your theme") }</li>
<li>{ _t("%(brand)s URL", { brand }) }</li>
<li>{ _t("Room ID") }</li>
<li>{ _t("Widget ID") }</li>
</ul>
</div>
);
@ -114,7 +114,7 @@ export default class AppPermission extends React.Component {
// Due to i18n limitations, we can't dedupe the code for variables in these two messages.
const warning = this.state.isWrapped
? _t("Using this widget may share data <helpIcon /> with %(widgetDomain)s & your Integration Manager.",
? _t("Using this widget may share data <helpIcon /> with %(widgetDomain)s & your integration manager.",
{ widgetDomain: this.state.widgetDomain }, { helpIcon: () => warningTooltip })
: _t("Using this widget may share data <helpIcon /> with %(widgetDomain)s.",
{ widgetDomain: this.state.widgetDomain }, { helpIcon: () => warningTooltip });
@ -124,22 +124,22 @@ export default class AppPermission extends React.Component {
return (
<div className='mx_AppPermissionWarning'>
<div className='mx_AppPermissionWarning_row mx_AppPermissionWarning_bolder mx_AppPermissionWarning_smallText'>
{_t("Widget added by")}
{ _t("Widget added by") }
</div>
<div className='mx_AppPermissionWarning_row'>
{avatar}
<h4 className='mx_AppPermissionWarning_bolder'>{displayName}</h4>
<div className='mx_AppPermissionWarning_smallText'>{userId}</div>
{ avatar }
<h4 className='mx_AppPermissionWarning_bolder'>{ displayName }</h4>
<div className='mx_AppPermissionWarning_smallText'>{ userId }</div>
</div>
<div className='mx_AppPermissionWarning_row mx_AppPermissionWarning_smallText'>
{warning}
{ warning }
</div>
<div className='mx_AppPermissionWarning_row mx_AppPermissionWarning_smallText'>
{_t("This widget may use cookies.")}&nbsp;{encryptionWarning}
{ _t("This widget may use cookies.") }&nbsp;{ encryptionWarning }
</div>
<div className='mx_AppPermissionWarning_row'>
<AccessibleButton kind='primary_sm' onClick={this.props.onPermissionGranted}>
{_t("Continue")}
{ _t("Continue") }
</AccessibleButton>
</div>
</div>

View file

@ -238,6 +238,7 @@ export default class AppTile extends React.Component {
case 'm.sticker':
if (this._sgWidget.widgetApi.hasCapability(MatrixCapabilities.StickerSending)) {
dis.dispatch({ action: 'post_sticker_message', data: payload.data });
dis.dispatch({ action: 'stickerpicker_close' });
} else {
console.warn('Ignoring sticker message. Invalid capability');
}
@ -407,7 +408,7 @@ export default class AppTile extends React.Component {
// AppTile's border is in the wrong place
appTileBody = <div className="mx_AppTile_persistedWrapper">
<PersistedElement persistKey={this._persistKey}>
{appTileBody}
{ appTileBody }
</PersistedElement>
</div>;
}
@ -452,13 +453,13 @@ export default class AppTile extends React.Component {
title={_t('Popout widget')}
onClick={this._onPopoutWidgetClick}
/> }
{ <ContextMenuButton
<ContextMenuButton
className="mx_AppTileMenuBar_iconButton mx_AppTileMenuBar_iconButton_menu"
label={_t("Options")}
isExpanded={this.state.menuDisplayed}
inputRef={this._contextMenuButton}
onClick={this._onContextMenuClick}
/> }
/>
</span>
</div> }
{ appTileBody }

View file

@ -41,6 +41,6 @@ export default function DNDTagTile(props) {
menuDisplayed={menuDisplayed}
openMenu={openMenu}
/>
{contextMenu}
{ contextMenu }
</>;
}

View file

@ -38,7 +38,7 @@ export default function DesktopBuildsNotice({ isRoomEncrypted, kind }: IProps) {
if (EventIndexPeg.error) {
return <>
{_t("Message search initialisation failed, check <a>your settings</a> for more information", {}, {
{ _t("Message search initialisation failed, check <a>your settings</a> for more information", {}, {
a: sub => (<a onClick={(evt) => {
evt.preventDefault();
dis.dispatch({
@ -46,9 +46,9 @@ export default function DesktopBuildsNotice({ isRoomEncrypted, kind }: IProps) {
initialTabId: UserTab.Security,
});
}}>
{sub}
{ sub }
</a>),
})}
}) }
</>;
}
@ -61,12 +61,12 @@ export default function DesktopBuildsNotice({ isRoomEncrypted, kind }: IProps) {
switch (kind) {
case WarningKind.Files:
text = _t("Use the <a>Desktop app</a> to see all encrypted files", {}, {
a: sub => (<a href={desktopBuilds.url} target="_blank" rel="noreferrer noopener">{sub}</a>),
a: sub => (<a href={desktopBuilds.url} target="_blank" rel="noreferrer noopener">{ sub }</a>),
});
break;
case WarningKind.Search:
text = _t("Use the <a>Desktop app</a> to search encrypted messages", {}, {
a: sub => (<a href={desktopBuilds.url} target="_blank" rel="noreferrer noopener">{sub}</a>),
a: sub => (<a href={desktopBuilds.url} target="_blank" rel="noreferrer noopener">{ sub }</a>),
});
break;
}
@ -89,8 +89,8 @@ export default function DesktopBuildsNotice({ isRoomEncrypted, kind }: IProps) {
return (
<div className="mx_DesktopBuildsNotice">
{logo}
<span>{text}</span>
{ logo }
<span>{ text }</span>
</div>
);
}

View file

@ -56,7 +56,7 @@ export class ExistingSource extends React.Component<DesktopCapturerSourceIProps>
className="mx_desktopCapturerSourcePicker_stream_thumbnail"
src={this.props.source.thumbnailURL}
/>
<span className="mx_desktopCapturerSourcePicker_stream_name">{this.props.source.name}</span>
<span className="mx_desktopCapturerSourcePicker_stream_name">{ this.props.source.name }</span>
</AccessibleButton>
);
}
@ -157,13 +157,13 @@ export default class DesktopCapturerSourcePicker extends React.Component<
className={screensButtonStyle}
onClick={this.onScreensClick}
>
{_t("Screens")}
{ _t("Screens") }
</AccessibleButton>
<AccessibleButton
className={windowsButtonStyle}
onClick={this.onWindowsClick}
>
{_t("Windows")}
{ _t("Windows") }
</AccessibleButton>
</div>
<div className="mx_desktopCapturerSourcePicker_panel">

View file

@ -0,0 +1,31 @@
/*
Copyright 2021 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
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 * as React from "react";
import AccessibleButton from "./AccessibleButton";
interface IProps {
// Callback for when the button is pressed
onBackspacePress: () => void;
}
export default class DialPadBackspaceButton extends React.PureComponent<IProps> {
render() {
return <div className="mx_DialPadBackspaceButtonWrapper">
<AccessibleButton className="mx_DialPadBackspaceButton" onClick={this.props.onBackspacePress} />
</div>;
}
}

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