Merge branch 'develop' into t3chguy/fix/18071
This commit is contained in:
commit
39d9ec3ced
464 changed files with 10033 additions and 6253 deletions
|
@ -14,9 +14,7 @@ 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";
|
||||
|
@ -25,44 +23,13 @@ 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;
|
||||
}
|
||||
import AudioPlayerBase from "./AudioPlayerBase";
|
||||
|
||||
@replaceableComponent("views.audio_messages.AudioPlayer")
|
||||
export default class AudioPlayer extends React.PureComponent<IProps, IState> {
|
||||
export default class AudioPlayer extends AudioPlayerBase {
|
||||
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
|
||||
|
@ -88,37 +55,39 @@ export default class AudioPlayer extends React.PureComponent<IProps, IState> {
|
|||
return `(${formatBytes(bytes)})`;
|
||||
}
|
||||
|
||||
public render(): ReactNode {
|
||||
protected renderComponent(): 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} />
|
||||
{/* easiest way to introduce a gap between the components */}
|
||||
{ this.renderFileSize() }
|
||||
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} />
|
||||
{ /* 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>
|
||||
<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>;
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
70
src/components/views/audio_messages/AudioPlayerBase.tsx
Normal file
70
src/components/views/audio_messages/AudioPlayerBase.tsx
Normal file
|
@ -0,0 +1,70 @@
|
|||
/*
|
||||
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 "../../../audio/Playback";
|
||||
import { TileShape } from "../rooms/EventTile";
|
||||
import React, { ReactNode } from "react";
|
||||
import { UPDATE_EVENT } from "../../../stores/AsyncStore";
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import { _t } from "../../../languageHandler";
|
||||
|
||||
interface IProps {
|
||||
// Playback instance to render. Cannot change during component lifecycle: create
|
||||
// an all-new component instead.
|
||||
playback: Playback;
|
||||
|
||||
mediaName?: string;
|
||||
tileShape?: TileShape;
|
||||
}
|
||||
|
||||
interface IState {
|
||||
playbackPhase: PlaybackState;
|
||||
error?: boolean;
|
||||
}
|
||||
|
||||
@replaceableComponent("views.audio_messages.AudioPlayerBase")
|
||||
export default abstract class AudioPlayerBase extends React.PureComponent<IProps, IState> {
|
||||
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.
|
||||
this.props.playback.prepare().catch(e => {
|
||||
console.error("Error processing audio file:", e);
|
||||
this.setState({ error: true });
|
||||
});
|
||||
}
|
||||
|
||||
private onPlaybackUpdate = (ev: PlaybackState) => {
|
||||
this.setState({ playbackPhase: ev });
|
||||
};
|
||||
|
||||
protected abstract renderComponent(): ReactNode;
|
||||
|
||||
public render(): ReactNode {
|
||||
return <>
|
||||
{ this.renderComponent() }
|
||||
{ this.state.error && <div className="text-warning">{ _t("Error downloading audio") }</div> }
|
||||
</>;
|
||||
}
|
||||
}
|
|
@ -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>;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@ limitations under the License.
|
|||
import React from "react";
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import Clock from "./Clock";
|
||||
import { Playback } from "../../../voice/Playback";
|
||||
import { Playback } from "../../../audio/Playback";
|
||||
|
||||
interface IProps {
|
||||
playback: Playback;
|
||||
|
|
|
@ -15,7 +15,7 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
import React from "react";
|
||||
import { IRecordingUpdate, VoiceRecording } from "../../../voice/VoiceRecording";
|
||||
import { IRecordingUpdate, VoiceRecording } from "../../../audio/VoiceRecording";
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import Clock from "./Clock";
|
||||
import { MarkedExecution } from "../../../utils/MarkedExecution";
|
||||
|
|
|
@ -15,7 +15,7 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
import React from "react";
|
||||
import { IRecordingUpdate, RECORDING_PLAYBACK_SAMPLES, VoiceRecording } from "../../../voice/VoiceRecording";
|
||||
import { IRecordingUpdate, RECORDING_PLAYBACK_SAMPLES, VoiceRecording } from "../../../audio/VoiceRecording";
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import { arrayFastResample } from "../../../utils/arrays";
|
||||
import { percentageOf } from "../../../utils/numbers";
|
||||
|
|
|
@ -18,7 +18,7 @@ import React, { ReactNode } from "react";
|
|||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";
|
||||
import { _t } from "../../../languageHandler";
|
||||
import { Playback, PlaybackState } from "../../../voice/Playback";
|
||||
import { Playback, PlaybackState } from "../../../audio/Playback";
|
||||
import classNames from "classnames";
|
||||
|
||||
// omitted props are handled by render function
|
||||
|
|
|
@ -17,7 +17,7 @@ limitations under the License.
|
|||
import React from "react";
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import Clock from "./Clock";
|
||||
import { Playback, PlaybackState } from "../../../voice/Playback";
|
||||
import { Playback, PlaybackState } from "../../../audio/Playback";
|
||||
import { UPDATE_EVENT } from "../../../stores/AsyncStore";
|
||||
|
||||
interface IProps {
|
||||
|
|
|
@ -18,7 +18,7 @@ import React from "react";
|
|||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import { arraySeed, arrayTrimFill } from "../../../utils/arrays";
|
||||
import Waveform from "./Waveform";
|
||||
import { Playback, PLAYBACK_WAVEFORM_SAMPLES } from "../../../voice/Playback";
|
||||
import { Playback, PLAYBACK_WAVEFORM_SAMPLES } from "../../../audio/Playback";
|
||||
import { percentageOf } from "../../../utils/numbers";
|
||||
|
||||
interface IProps {
|
||||
|
|
|
@ -14,61 +14,30 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { Playback, PlaybackState } from "../../../voice/Playback";
|
||||
import React, { ReactNode } from "react";
|
||||
import { UPDATE_EVENT } from "../../../stores/AsyncStore";
|
||||
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;
|
||||
}
|
||||
import AudioPlayerBase from "./AudioPlayerBase";
|
||||
|
||||
@replaceableComponent("views.audio_messages.RecordingPlayback")
|
||||
export default class RecordingPlayback extends React.PureComponent<IProps, IState> {
|
||||
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();
|
||||
}
|
||||
|
||||
export default class RecordingPlayback extends AudioPlayerBase {
|
||||
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 {
|
||||
protected renderComponent(): ReactNode {
|
||||
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} />
|
||||
{ this.isWaveformable && <PlaybackWaveform playback={this.props.playback} /> }
|
||||
</div>;
|
||||
return (
|
||||
<div className={'mx_MediaBody mx_VoiceMessagePrimaryContainer ' + shapeClass}>
|
||||
<PlayPauseButton playback={this.props.playback} playbackPhase={this.state.playbackPhase} />
|
||||
<PlaybackClock playback={this.props.playback} />
|
||||
{ this.isWaveformable && <PlaybackWaveform playback={this.props.playback} /> }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { Playback, PlaybackState } from "../../../voice/Playback";
|
||||
import { Playback, PlaybackState } from "../../../audio/Playback";
|
||||
import React, { ChangeEvent, CSSProperties, ReactNode } from "react";
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import { MarkedExecution } from "../../../utils/MarkedExecution";
|
||||
|
|
|
@ -47,17 +47,21 @@ 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({
|
||||
'mx_Waveform_bar': true,
|
||||
'mx_Waveform_bar_100pct': isCompleteBar,
|
||||
});
|
||||
return <span key={i} style={{
|
||||
"--barHeight": h,
|
||||
} as WaveformCSSProperties} className={classes} />;
|
||||
})}
|
||||
return <span
|
||||
key={i}
|
||||
style={{
|
||||
"--barHeight": h,
|
||||
} as WaveformCSSProperties}
|
||||
className={classes}
|
||||
/>;
|
||||
}) }
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>;
|
|
@ -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>
|
|
@ -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 />
|
|
@ -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>;
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>;
|
|
@ -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,
|
||||
};
|
|
@ -416,13 +416,15 @@ export class TermsAuthEntry extends React.Component<ITermsAuthEntryProps, ITerms
|
|||
let submitButton;
|
||||
if (this.props.showContinue !== false) {
|
||||
// XXX: button classes
|
||||
submitButton = <button className="mx_InteractiveAuthEntryComponents_termsSubmit mx_GeneralButton"
|
||||
onClick={this.trySubmit} disabled={!allChecked}>{_t("Accept")}</button>;
|
||||
submitButton = <button
|
||||
className="mx_InteractiveAuthEntryComponents_termsSubmit mx_GeneralButton"
|
||||
onClick={this.trySubmit}
|
||||
disabled={!allChecked}>{ _t("Accept") }</button>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<p>{_t("Please review and accept the policies of this homeserver:")}</p>
|
||||
<p>{ _t("Please review and accept the policies of this homeserver:") }</p>
|
||||
{ checkboxes }
|
||||
{ errorSection }
|
||||
{ submitButton }
|
||||
|
@ -613,15 +615,17 @@ 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")}
|
||||
<input
|
||||
type="submit"
|
||||
value={_t("Submit")}
|
||||
className={submitClasses}
|
||||
disabled={!enableSubmit}
|
||||
/>
|
||||
</form>
|
||||
{errorSection}
|
||||
{ errorSection }
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -717,21 +721,21 @@ export class SSOAuthEntry extends React.Component<ISSOAuthEntryProps, ISSOAuthEn
|
|||
<AccessibleButton
|
||||
onClick={this.props.onCancel}
|
||||
kind={this.props.continueKind ? (this.props.continueKind + '_outline') : 'primary_outline'}
|
||||
>{_t("Cancel")}</AccessibleButton>
|
||||
>{ _t("Cancel") }</AccessibleButton>
|
||||
);
|
||||
if (this.state.phase === SSOAuthEntry.PHASE_PREAUTH) {
|
||||
continueButton = (
|
||||
<AccessibleButton
|
||||
onClick={this.onStartAuthClick}
|
||||
kind={this.props.continueKind || 'primary'}
|
||||
>{this.props.continueText || _t("Single Sign On")}</AccessibleButton>
|
||||
>{ this.props.continueText || _t("Single Sign On") }</AccessibleButton>
|
||||
);
|
||||
} else {
|
||||
continueButton = (
|
||||
<AccessibleButton
|
||||
onClick={this.onConfirmClick}
|
||||
kind={this.props.continueKind || 'primary'}
|
||||
>{this.props.continueText || _t("Confirm")}</AccessibleButton>
|
||||
>{ this.props.continueText || _t("Confirm") }</AccessibleButton>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -753,8 +757,8 @@ export class SSOAuthEntry extends React.Component<ISSOAuthEntryProps, ISSOAuthEn
|
|||
return <React.Fragment>
|
||||
{ errorSection }
|
||||
<div className="mx_InteractiveAuthEntryComponents_sso_buttons">
|
||||
{cancelButton}
|
||||
{continueButton}
|
||||
{ cancelButton }
|
||||
{ continueButton }
|
||||
</div>
|
||||
</React.Fragment>;
|
||||
}
|
||||
|
@ -825,7 +829,7 @@ export class FallbackAuthEntry extends React.Component<IAuthEntryProps> {
|
|||
<a href="" ref={this.fallbackButton} onClick={this.onShowFallbackClick}>{
|
||||
_t("Start authentication")
|
||||
}</a>
|
||||
{errorSection}
|
||||
{ errorSection }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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}
|
|
@ -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')}
|
||||
|
|
|
@ -537,15 +537,15 @@ export default class RegistrationForm extends React.PureComponent<IProps, IState
|
|||
<div>
|
||||
<form onSubmit={this.onSubmit}>
|
||||
<div className="mx_AuthBody_fieldRow">
|
||||
{this.renderUsername()}
|
||||
{ this.renderUsername() }
|
||||
</div>
|
||||
<div className="mx_AuthBody_fieldRow">
|
||||
{this.renderPassword()}
|
||||
{this.renderPasswordConfirm()}
|
||||
{ this.renderPassword() }
|
||||
{ this.renderPasswordConfirm() }
|
||||
</div>
|
||||
<div className="mx_AuthBody_fieldRow">
|
||||
{this.renderEmail()}
|
||||
{this.renderPhoneNumber()}
|
||||
{ this.renderEmail() }
|
||||
{ this.renderPhoneNumber() }
|
||||
</div>
|
||||
{ emailHelperText }
|
||||
{ registerButton }
|
||||
|
|
|
@ -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;
|
|
@ -187,7 +187,8 @@ const BaseAvatar = (props: IProps) => {
|
|||
width: toPx(width),
|
||||
height: toPx(height),
|
||||
}}
|
||||
title={title} alt={_t("Avatar")}
|
||||
title={title}
|
||||
alt={_t("Avatar")}
|
||||
inputRef={inputRef}
|
||||
{...otherProps} />
|
||||
);
|
||||
|
@ -201,7 +202,8 @@ const BaseAvatar = (props: IProps) => {
|
|||
width: toPx(width),
|
||||
height: toPx(height),
|
||||
}}
|
||||
title={title} alt=""
|
||||
title={title}
|
||||
alt=""
|
||||
ref={inputRef}
|
||||
{...otherProps} />
|
||||
);
|
||||
|
|
|
@ -205,8 +205,8 @@ export default class DecoratedRoomAvatar extends React.PureComponent<IProps, ISt
|
|||
oobData={this.props.oobData}
|
||||
viewAvatarOnClick={this.props.viewAvatarOnClick}
|
||||
/>
|
||||
{icon}
|
||||
{badge}
|
||||
{ icon }
|
||||
{ badge }
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -102,8 +102,12 @@ export default class MemberAvatar extends React.Component<IProps, IState> {
|
|||
}
|
||||
|
||||
return (
|
||||
<BaseAvatar {...otherProps} name={this.state.name} title={this.state.title}
|
||||
idName={userId} url={this.state.imageUrl} onClick={onClick} />
|
||||
<BaseAvatar {...otherProps}
|
||||
name={this.state.name}
|
||||
title={this.state.title}
|
||||
idName={userId}
|
||||
url={this.state.imageUrl}
|
||||
onClick={onClick} />
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -145,7 +145,7 @@ export default class MemberStatusMessageAvatar extends React.Component {
|
|||
isExpanded={this.state.menuDisplayed}
|
||||
label={_t("User Status")}
|
||||
>
|
||||
{avatar}
|
||||
{ avatar }
|
||||
</ContextMenuButton>
|
||||
|
||||
{ contextMenu }
|
||||
|
|
|
@ -13,9 +13,11 @@ 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, { ComponentProps } from 'react';
|
||||
import { Room } from 'matrix-js-sdk/src/models/room';
|
||||
import { ResizeMethod } from 'matrix-js-sdk/src/@types/partials';
|
||||
import classNames from "classnames";
|
||||
|
||||
import BaseAvatar from './BaseAvatar';
|
||||
import ImageView from '../elements/ImageView';
|
||||
|
@ -32,11 +34,14 @@ interface IProps extends Omit<ComponentProps<typeof BaseAvatar>, "name" | "idNam
|
|||
// oobData.avatarUrl should be set (else there
|
||||
// would be nowhere to get the avatar from)
|
||||
room?: Room;
|
||||
oobData?: IOOBData;
|
||||
oobData?: IOOBData & {
|
||||
roomId?: string;
|
||||
};
|
||||
width?: number;
|
||||
height?: number;
|
||||
resizeMethod?: ResizeMethod;
|
||||
viewAvatarOnClick?: boolean;
|
||||
className?: string;
|
||||
onClick?(): void;
|
||||
}
|
||||
|
||||
|
@ -129,15 +134,19 @@ export default class RoomAvatar extends React.Component<IProps, IState> {
|
|||
};
|
||||
|
||||
public render() {
|
||||
const { room, oobData, viewAvatarOnClick, onClick, ...otherProps } = this.props;
|
||||
const { room, oobData, viewAvatarOnClick, onClick, className, ...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;
|
||||
const idName = room ? (DMRoomMap.shared().getUserIdForRoomId(room.roomId) ?? room.roomId) : oobData.roomId;
|
||||
|
||||
return (
|
||||
<BaseAvatar {...otherProps}
|
||||
<BaseAvatar
|
||||
{...otherProps}
|
||||
className={classNames(className, {
|
||||
mx_RoomAvatar_isSpaceRoom: room?.isSpaceRoom(),
|
||||
})}
|
||||
name={roomName}
|
||||
idName={idName}
|
||||
urls={this.state.urls}
|
||||
|
|
|
@ -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>;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -60,8 +60,10 @@ export default class DialpadContextMenu extends React.Component<IProps, IState>
|
|||
<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}
|
||||
<Field
|
||||
className="mx_DialPadContextMenu_dialled"
|
||||
value={this.state.value}
|
||||
autoFocus={true}
|
||||
onChange={this.onChange}
|
||||
/>
|
||||
</div>
|
||||
|
|
|
@ -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>;
|
||||
};
|
||||
|
||||
|
|
|
@ -43,11 +43,15 @@ export function canCancel(eventStatus: EventStatus): boolean {
|
|||
return eventStatus === EventStatus.QUEUED || eventStatus === EventStatus.NOT_SENT;
|
||||
}
|
||||
|
||||
interface IEventTileOps {
|
||||
export interface IEventTileOps {
|
||||
isWidgetHidden(): boolean;
|
||||
unhideWidget(): void;
|
||||
}
|
||||
|
||||
export interface IOperableEventTile {
|
||||
getEventTileOps(): IEventTileOps;
|
||||
}
|
||||
|
||||
interface IProps {
|
||||
/* the MatrixEvent associated with the context menu */
|
||||
mxEvent: MatrixEvent;
|
||||
|
@ -268,7 +272,7 @@ export default class MessageContextMenu extends React.Component<IProps, IState>
|
|||
resendReactionsButton = (
|
||||
<IconizedContextMenuOption
|
||||
iconClassName="mx_MessageContextMenu_iconResend"
|
||||
label={ _t('Resend %(unsentCount)s reaction(s)', { unsentCount: unsentReactionsCount }) }
|
||||
label={_t('Resend %(unsentCount)s reaction(s)', { unsentCount: unsentReactionsCount })}
|
||||
onClick={this.onResendReactionsClick}
|
||||
/>
|
||||
);
|
||||
|
@ -298,7 +302,7 @@ export default class MessageContextMenu extends React.Component<IProps, IState>
|
|||
pinButton = (
|
||||
<IconizedContextMenuOption
|
||||
iconClassName="mx_MessageContextMenu_iconPin"
|
||||
label={ this.isPinned() ? _t('Unpin') : _t('Pin') }
|
||||
label={this.isPinned() ? _t('Unpin') : _t('Pin')}
|
||||
onClick={this.onPinClick}
|
||||
/>
|
||||
);
|
||||
|
@ -333,7 +337,7 @@ export default class MessageContextMenu extends React.Component<IProps, IState>
|
|||
<IconizedContextMenuOption
|
||||
iconClassName="mx_MessageContextMenu_iconPermalink"
|
||||
onClick={this.onPermalinkClick}
|
||||
label= {_t('Share')}
|
||||
label={_t('Share')}
|
||||
element="a"
|
||||
{
|
||||
// XXX: Typescript signature for AccessibleButton doesn't work properly for non-inputs like `a`
|
||||
|
@ -364,7 +368,7 @@ export default class MessageContextMenu extends React.Component<IProps, IState>
|
|||
<IconizedContextMenuOption
|
||||
iconClassName="mx_MessageContextMenu_iconLink"
|
||||
onClick={this.closeMenu}
|
||||
label={ _t('Source URL') }
|
||||
label={_t('Source URL')}
|
||||
element="a"
|
||||
{
|
||||
// XXX: Typescript signature for AccessibleButton doesn't work properly for non-inputs like `a`
|
||||
|
|
|
@ -99,20 +99,22 @@ 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}
|
||||
actionButton = <AccessibleButton
|
||||
className="mx_StatusMessageContextMenu_submit"
|
||||
disabled={!this.state.message}
|
||||
onClick={this._onSubmit}
|
||||
>
|
||||
<span>{_t("Set status")}</span>
|
||||
<span>{ _t("Set status") }</span>
|
||||
</AccessibleButton>;
|
||||
}
|
||||
|
||||
|
@ -121,17 +123,24 @@ export default class StatusMessageContextMenu extends React.Component {
|
|||
spinner = <Spinner w="24" h="24" />;
|
||||
}
|
||||
|
||||
const form = <form className="mx_StatusMessageContextMenu_form"
|
||||
autoComplete="off" onSubmit={this._onSubmit}
|
||||
const form = <form
|
||||
className="mx_StatusMessageContextMenu_form"
|
||||
autoComplete="off"
|
||||
onSubmit={this._onSubmit}
|
||||
>
|
||||
<input type="text" className="mx_StatusMessageContextMenu_message"
|
||||
key="message" placeholder={_t("Set a new status...")}
|
||||
autoFocus={true} maxLength="60" value={this.state.message}
|
||||
<input
|
||||
type="text"
|
||||
className="mx_StatusMessageContextMenu_message"
|
||||
key="message"
|
||||
placeholder={_t("Set a new status...")}
|
||||
autoFocus={true}
|
||||
maxLength="60"
|
||||
value={this.state.message}
|
||||
onChange={this._onStatusChange}
|
||||
/>
|
||||
<div className="mx_StatusMessageContextMenu_actionContainer">
|
||||
{actionButton}
|
||||
{spinner}
|
||||
{ actionButton }
|
||||
{ spinner }
|
||||
</div>
|
||||
</form>;
|
||||
|
||||
|
|
|
@ -76,7 +76,8 @@ const WidgetContextMenu: React.FC<IProps> = ({
|
|||
onFinished();
|
||||
};
|
||||
streamAudioStreamButton = <IconizedContextMenuOption
|
||||
onClick={onStreamAudioClick} label={_t("Start audio stream")}
|
||||
onClick={onStreamAudioClick}
|
||||
label={_t("Start audio stream")}
|
||||
/>;
|
||||
}
|
||||
|
||||
|
|
|
@ -17,7 +17,6 @@ limitations under the License.
|
|||
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';
|
||||
|
@ -44,9 +43,8 @@ import EntityTile from "../rooms/EntityTile";
|
|||
import BaseAvatar from "../avatars/BaseAvatar";
|
||||
|
||||
interface IProps extends IDialogProps {
|
||||
matrixClient: MatrixClient;
|
||||
space: Room;
|
||||
onCreateRoomClick(cli: MatrixClient, space: Room): void;
|
||||
onCreateRoomClick(space: Room): void;
|
||||
}
|
||||
|
||||
export const Entry = ({ room, checked, onChange }) => {
|
||||
|
@ -211,17 +209,23 @@ export const AddExistingToSpace: React.FC<IAddExistingToSpaceProps> = ({
|
|||
function overflowTile(overflowCount, totalCount) {
|
||||
const text = _t("and %(count)s others...", { count: overflowCount });
|
||||
return (
|
||||
<EntityTile className="mx_EntityTile_ellipsis" avatarJsx={
|
||||
<BaseAvatar url={require("../../../../res/img/ellipsis.svg")} name="..." width={36} height={36} />
|
||||
} name={text} presenceState="online" suppressOnHover={true}
|
||||
onClick={() => setTruncateAt(totalCount)} />
|
||||
<EntityTile
|
||||
className="mx_EntityTile_ellipsis"
|
||||
avatarJsx={
|
||||
<BaseAvatar url={require("../../../../res/img/ellipsis.svg")} name="..." width={36} height={36} />
|
||||
}
|
||||
name={text}
|
||||
presenceState="online"
|
||||
suppressOnHover={true}
|
||||
onClick={() => setTruncateAt(totalCount)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
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}
|
||||
|
@ -295,7 +299,7 @@ export const AddExistingToSpace: React.FC<IAddExistingToSpaceProps> = ({
|
|||
</div>;
|
||||
};
|
||||
|
||||
const AddExistingToSpaceDialog: React.FC<IProps> = ({ matrixClient: cli, space, onCreateRoomClick, onFinished }) => {
|
||||
const AddExistingToSpaceDialog: React.FC<IProps> = ({ space, onCreateRoomClick, onFinished }) => {
|
||||
const [selectedSpace, setSelectedSpace] = useState(space);
|
||||
const existingSubspaces = SpaceStore.instance.getChildSpaces(space.roomId);
|
||||
|
||||
|
@ -344,13 +348,13 @@ const AddExistingToSpaceDialog: React.FC<IProps> = ({ matrixClient: cli, space,
|
|||
onFinished={onFinished}
|
||||
fixedWidth={false}
|
||||
>
|
||||
<MatrixClientContext.Provider value={cli}>
|
||||
<MatrixClientContext.Provider value={space.client}>
|
||||
<AddExistingToSpace
|
||||
space={space}
|
||||
onFinished={onFinished}
|
||||
footerPrompt={<>
|
||||
<div>{ _t("Want to add a new room instead?") }</div>
|
||||
<AccessibleButton onClick={() => onCreateRoomClick(cli, space)} kind="link">
|
||||
<AccessibleButton onClick={() => onCreateRoomClick(space)} kind="link">
|
||||
{ _t("Create a new room") }
|
||||
</AccessibleButton>
|
||||
</>}
|
||||
|
|
|
@ -18,14 +18,12 @@ 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';
|
||||
import { MatrixClientPeg } from '../../../MatrixClientPeg';
|
||||
import dis from '../../../dispatcher/dispatcher';
|
||||
import { addressTypes, getAddressType } from '../../../UserAddress';
|
||||
import { AddressType, addressTypes, getAddressType, IUserAddress } from '../../../UserAddress';
|
||||
import GroupStore from '../../../stores/GroupStore';
|
||||
import * as Email from '../../../email';
|
||||
import IdentityAuthClient from '../../../IdentityAuthClient';
|
||||
|
@ -34,6 +32,10 @@ import { abbreviateUrl } from '../../../utils/UrlUtils';
|
|||
import { Key } from "../../../Keyboard";
|
||||
import { Action } from "../../../dispatcher/actions";
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import AddressSelector from '../elements/AddressSelector';
|
||||
import AddressTile from '../elements/AddressTile';
|
||||
import BaseDialog from "./BaseDialog";
|
||||
import DialogButtons from "../elements/DialogButtons";
|
||||
|
||||
const TRUNCATE_QUERY_LIST = 40;
|
||||
const QUERY_USER_DIRECTORY_DEBOUNCE_MS = 200;
|
||||
|
@ -44,29 +46,64 @@ const addressTypeName = {
|
|||
'email': _td("email address"),
|
||||
};
|
||||
|
||||
@replaceableComponent("views.dialogs.AddressPickerDialog")
|
||||
export default class AddressPickerDialog extends React.Component {
|
||||
static propTypes = {
|
||||
title: PropTypes.string.isRequired,
|
||||
description: PropTypes.node,
|
||||
// Extra node inserted after picker input, dropdown and errors
|
||||
extraNode: PropTypes.node,
|
||||
value: PropTypes.string,
|
||||
placeholder: PropTypes.oneOfType([PropTypes.string, PropTypes.func]),
|
||||
roomId: PropTypes.string,
|
||||
button: PropTypes.string,
|
||||
focus: PropTypes.bool,
|
||||
validAddressTypes: PropTypes.arrayOf(PropTypes.oneOf(addressTypes)),
|
||||
onFinished: PropTypes.func.isRequired,
|
||||
groupId: PropTypes.string,
|
||||
// The type of entity to search for. Default: 'user'.
|
||||
pickerType: PropTypes.oneOf(['user', 'room']),
|
||||
// Whether the current user should be included in the addresses returned. Only
|
||||
// applicable when pickerType is `user`. Default: false.
|
||||
includeSelf: PropTypes.bool,
|
||||
};
|
||||
interface IResult {
|
||||
user_id: string; // eslint-disable-line camelcase
|
||||
room_id?: string; // eslint-disable-line camelcase
|
||||
name?: string;
|
||||
display_name?: string; // eslint-disable-line camelcase
|
||||
avatar_url?: string;// eslint-disable-line camelcase
|
||||
}
|
||||
|
||||
static defaultProps = {
|
||||
interface IProps {
|
||||
title: string;
|
||||
description?: JSX.Element;
|
||||
// Extra node inserted after picker input, dropdown and errors
|
||||
extraNode?: JSX.Element;
|
||||
value?: string;
|
||||
placeholder?: ((validAddressTypes: any) => string) | string;
|
||||
roomId?: string;
|
||||
button?: string;
|
||||
focus?: boolean;
|
||||
validAddressTypes?: AddressType[];
|
||||
onFinished: (success: boolean, list?: IUserAddress[]) => void;
|
||||
groupId?: string;
|
||||
// The type of entity to search for. Default: 'user'.
|
||||
pickerType?: 'user' | 'room';
|
||||
// Whether the current user should be included in the addresses returned. Only
|
||||
// applicable when pickerType is `user`. Default: false.
|
||||
includeSelf?: boolean;
|
||||
}
|
||||
|
||||
interface IState {
|
||||
// Whether to show an error message because of an invalid address
|
||||
invalidAddressError: boolean;
|
||||
// List of UserAddressType objects representing
|
||||
// the list of addresses we're going to invite
|
||||
selectedList: IUserAddress[];
|
||||
// Whether a search is ongoing
|
||||
busy: boolean;
|
||||
// An error message generated during the user directory search
|
||||
searchError: string;
|
||||
// Whether the server supports the user_directory API
|
||||
serverSupportsUserDirectory: boolean;
|
||||
// The query being searched for
|
||||
query: string;
|
||||
// List of UserAddressType objects representing the set of
|
||||
// auto-completion results for the current search query.
|
||||
suggestedList: IUserAddress[];
|
||||
// List of address types initialised from props, but may change while the
|
||||
// dialog is open and represents the supported list of address types at this time.
|
||||
validAddressTypes: AddressType[];
|
||||
}
|
||||
|
||||
@replaceableComponent("views.dialogs.AddressPickerDialog")
|
||||
export default class AddressPickerDialog extends React.Component<IProps, IState> {
|
||||
private textinput = createRef<HTMLTextAreaElement>();
|
||||
private addressSelector = createRef<AddressSelector>();
|
||||
private queryChangedDebouncer: number;
|
||||
private cancelThreepidLookup: () => void;
|
||||
|
||||
static defaultProps: Partial<IProps> = {
|
||||
value: "",
|
||||
focus: true,
|
||||
validAddressTypes: addressTypes,
|
||||
|
@ -74,36 +111,23 @@ export default class AddressPickerDialog extends React.Component {
|
|||
includeSelf: false,
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
constructor(props: IProps) {
|
||||
super(props);
|
||||
|
||||
this._textinput = createRef();
|
||||
|
||||
let validAddressTypes = this.props.validAddressTypes;
|
||||
// Remove email from validAddressTypes if no IS is configured. It may be added at a later stage by the user
|
||||
if (!MatrixClientPeg.get().getIdentityServerUrl() && validAddressTypes.includes("email")) {
|
||||
validAddressTypes = validAddressTypes.filter(type => type !== "email");
|
||||
if (!MatrixClientPeg.get().getIdentityServerUrl() && validAddressTypes.includes(AddressType.Email)) {
|
||||
validAddressTypes = validAddressTypes.filter(type => type !== AddressType.Email);
|
||||
}
|
||||
|
||||
this.state = {
|
||||
// Whether to show an error message because of an invalid address
|
||||
invalidAddressError: false,
|
||||
// List of UserAddressType objects representing
|
||||
// the list of addresses we're going to invite
|
||||
selectedList: [],
|
||||
// Whether a search is ongoing
|
||||
busy: false,
|
||||
// An error message generated during the user directory search
|
||||
searchError: null,
|
||||
// Whether the server supports the user_directory API
|
||||
serverSupportsUserDirectory: true,
|
||||
// The query being searched for
|
||||
query: "",
|
||||
// List of UserAddressType objects representing the set of
|
||||
// auto-completion results for the current search query.
|
||||
suggestedList: [],
|
||||
// List of address types initialised from props, but may change while the
|
||||
// dialog is open and represents the supported list of address types at this time.
|
||||
validAddressTypes,
|
||||
};
|
||||
}
|
||||
|
@ -111,11 +135,11 @@ export default class AddressPickerDialog extends React.Component {
|
|||
componentDidMount() {
|
||||
if (this.props.focus) {
|
||||
// Set the cursor at the end of the text input
|
||||
this._textinput.current.value = this.props.value;
|
||||
this.textinput.current.value = this.props.value;
|
||||
}
|
||||
}
|
||||
|
||||
getPlaceholder() {
|
||||
private getPlaceholder(): string {
|
||||
const { placeholder } = this.props;
|
||||
if (typeof placeholder === "string") {
|
||||
return placeholder;
|
||||
|
@ -124,23 +148,23 @@ export default class AddressPickerDialog extends React.Component {
|
|||
return placeholder(this.state.validAddressTypes);
|
||||
}
|
||||
|
||||
onButtonClick = () => {
|
||||
private onButtonClick = (): void => {
|
||||
let selectedList = this.state.selectedList.slice();
|
||||
// Check the text input field to see if user has an unconverted address
|
||||
// If there is and it's valid add it to the local selectedList
|
||||
if (this._textinput.current.value !== '') {
|
||||
selectedList = this._addAddressesToList([this._textinput.current.value]);
|
||||
if (this.textinput.current.value !== '') {
|
||||
selectedList = this.addAddressesToList([this.textinput.current.value]);
|
||||
if (selectedList === null) return;
|
||||
}
|
||||
this.props.onFinished(true, selectedList);
|
||||
};
|
||||
|
||||
onCancel = () => {
|
||||
private onCancel = (): void => {
|
||||
this.props.onFinished(false);
|
||||
};
|
||||
|
||||
onKeyDown = e => {
|
||||
const textInput = this._textinput.current ? this._textinput.current.value : undefined;
|
||||
private onKeyDown = (e: React.KeyboardEvent): void => {
|
||||
const textInput = this.textinput.current ? this.textinput.current.value : undefined;
|
||||
|
||||
if (e.key === Key.ESCAPE) {
|
||||
e.stopPropagation();
|
||||
|
@ -149,15 +173,15 @@ export default class AddressPickerDialog extends React.Component {
|
|||
} else if (e.key === Key.ARROW_UP) {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
if (this.addressSelector) this.addressSelector.moveSelectionUp();
|
||||
if (this.addressSelector.current) this.addressSelector.current.moveSelectionUp();
|
||||
} else if (e.key === Key.ARROW_DOWN) {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
if (this.addressSelector) this.addressSelector.moveSelectionDown();
|
||||
if (this.addressSelector.current) this.addressSelector.current.moveSelectionDown();
|
||||
} else if (this.state.suggestedList.length > 0 && [Key.COMMA, Key.ENTER, Key.TAB].includes(e.key)) {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
if (this.addressSelector) this.addressSelector.chooseSelection();
|
||||
if (this.addressSelector.current) this.addressSelector.current.chooseSelection();
|
||||
} else if (textInput.length === 0 && this.state.selectedList.length && e.key === Key.BACKSPACE) {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
|
@ -169,17 +193,17 @@ export default class AddressPickerDialog extends React.Component {
|
|||
// if there's nothing in the input box, submit the form
|
||||
this.onButtonClick();
|
||||
} else {
|
||||
this._addAddressesToList([textInput]);
|
||||
this.addAddressesToList([textInput]);
|
||||
}
|
||||
} else if (textInput && (e.key === Key.COMMA || e.key === Key.TAB)) {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
this._addAddressesToList([textInput]);
|
||||
this.addAddressesToList([textInput]);
|
||||
}
|
||||
};
|
||||
|
||||
onQueryChanged = ev => {
|
||||
const query = ev.target.value;
|
||||
private onQueryChanged = (ev: React.ChangeEvent): void => {
|
||||
const query = (ev.target as HTMLTextAreaElement).value;
|
||||
if (this.queryChangedDebouncer) {
|
||||
clearTimeout(this.queryChangedDebouncer);
|
||||
}
|
||||
|
@ -188,17 +212,17 @@ export default class AddressPickerDialog extends React.Component {
|
|||
this.queryChangedDebouncer = setTimeout(() => {
|
||||
if (this.props.pickerType === 'user') {
|
||||
if (this.props.groupId) {
|
||||
this._doNaiveGroupSearch(query);
|
||||
this.doNaiveGroupSearch(query);
|
||||
} else if (this.state.serverSupportsUserDirectory) {
|
||||
this._doUserDirectorySearch(query);
|
||||
this.doUserDirectorySearch(query);
|
||||
} else {
|
||||
this._doLocalSearch(query);
|
||||
this.doLocalSearch(query);
|
||||
}
|
||||
} else if (this.props.pickerType === 'room') {
|
||||
if (this.props.groupId) {
|
||||
this._doNaiveGroupRoomSearch(query);
|
||||
this.doNaiveGroupRoomSearch(query);
|
||||
} else {
|
||||
this._doRoomSearch(query);
|
||||
this.doRoomSearch(query);
|
||||
}
|
||||
} else {
|
||||
console.error('Unknown pickerType', this.props.pickerType);
|
||||
|
@ -213,7 +237,7 @@ export default class AddressPickerDialog extends React.Component {
|
|||
}
|
||||
};
|
||||
|
||||
onDismissed = index => () => {
|
||||
private onDismissed = (index: number) => () => {
|
||||
const selectedList = this.state.selectedList.slice();
|
||||
selectedList.splice(index, 1);
|
||||
this.setState({
|
||||
|
@ -221,25 +245,21 @@ export default class AddressPickerDialog extends React.Component {
|
|||
suggestedList: [],
|
||||
query: "",
|
||||
});
|
||||
if (this._cancelThreepidLookup) this._cancelThreepidLookup();
|
||||
if (this.cancelThreepidLookup) this.cancelThreepidLookup();
|
||||
};
|
||||
|
||||
onClick = index => () => {
|
||||
this.onSelected(index);
|
||||
};
|
||||
|
||||
onSelected = index => {
|
||||
private onSelected = (index: number): void => {
|
||||
const selectedList = this.state.selectedList.slice();
|
||||
selectedList.push(this._getFilteredSuggestions()[index]);
|
||||
selectedList.push(this.getFilteredSuggestions()[index]);
|
||||
this.setState({
|
||||
selectedList,
|
||||
suggestedList: [],
|
||||
query: "",
|
||||
});
|
||||
if (this._cancelThreepidLookup) this._cancelThreepidLookup();
|
||||
if (this.cancelThreepidLookup) this.cancelThreepidLookup();
|
||||
};
|
||||
|
||||
_doNaiveGroupSearch(query) {
|
||||
private doNaiveGroupSearch(query: string): void {
|
||||
const lowerCaseQuery = query.toLowerCase();
|
||||
this.setState({
|
||||
busy: true,
|
||||
|
@ -260,7 +280,7 @@ export default class AddressPickerDialog extends React.Component {
|
|||
display_name: u.displayname,
|
||||
});
|
||||
});
|
||||
this._processResults(results, query);
|
||||
this.processResults(results, query);
|
||||
}).catch((err) => {
|
||||
console.error('Error whilst searching group rooms: ', err);
|
||||
this.setState({
|
||||
|
@ -273,7 +293,7 @@ export default class AddressPickerDialog extends React.Component {
|
|||
});
|
||||
}
|
||||
|
||||
_doNaiveGroupRoomSearch(query) {
|
||||
private doNaiveGroupRoomSearch(query: string): void {
|
||||
const lowerCaseQuery = query.toLowerCase();
|
||||
const results = [];
|
||||
GroupStore.getGroupRooms(this.props.groupId).forEach((r) => {
|
||||
|
@ -289,13 +309,13 @@ export default class AddressPickerDialog extends React.Component {
|
|||
name: r.name || r.canonical_alias,
|
||||
});
|
||||
});
|
||||
this._processResults(results, query);
|
||||
this.processResults(results, query);
|
||||
this.setState({
|
||||
busy: false,
|
||||
});
|
||||
}
|
||||
|
||||
_doRoomSearch(query) {
|
||||
private doRoomSearch(query: string): void {
|
||||
const lowerCaseQuery = query.toLowerCase();
|
||||
const rooms = MatrixClientPeg.get().getRooms();
|
||||
const results = [];
|
||||
|
@ -346,13 +366,13 @@ export default class AddressPickerDialog extends React.Component {
|
|||
return a.rank - b.rank;
|
||||
});
|
||||
|
||||
this._processResults(sortedResults, query);
|
||||
this.processResults(sortedResults, query);
|
||||
this.setState({
|
||||
busy: false,
|
||||
});
|
||||
}
|
||||
|
||||
_doUserDirectorySearch(query) {
|
||||
private doUserDirectorySearch(query: string): void {
|
||||
this.setState({
|
||||
busy: true,
|
||||
query,
|
||||
|
@ -366,7 +386,7 @@ export default class AddressPickerDialog extends React.Component {
|
|||
if (this.state.query !== query) {
|
||||
return;
|
||||
}
|
||||
this._processResults(resp.results, query);
|
||||
this.processResults(resp.results, query);
|
||||
}).catch((err) => {
|
||||
console.error('Error whilst searching user directory: ', err);
|
||||
this.setState({
|
||||
|
@ -377,7 +397,7 @@ export default class AddressPickerDialog extends React.Component {
|
|||
serverSupportsUserDirectory: false,
|
||||
});
|
||||
// Do a local search immediately
|
||||
this._doLocalSearch(query);
|
||||
this.doLocalSearch(query);
|
||||
}
|
||||
}).then(() => {
|
||||
this.setState({
|
||||
|
@ -386,7 +406,7 @@ export default class AddressPickerDialog extends React.Component {
|
|||
});
|
||||
}
|
||||
|
||||
_doLocalSearch(query) {
|
||||
private doLocalSearch(query: string): void {
|
||||
this.setState({
|
||||
query,
|
||||
searchError: null,
|
||||
|
@ -407,10 +427,10 @@ export default class AddressPickerDialog extends React.Component {
|
|||
avatar_url: user.avatarUrl,
|
||||
});
|
||||
});
|
||||
this._processResults(results, query);
|
||||
this.processResults(results, query);
|
||||
}
|
||||
|
||||
_processResults(results, query) {
|
||||
private processResults(results: IResult[], query: string): void {
|
||||
const suggestedList = [];
|
||||
results.forEach((result) => {
|
||||
if (result.room_id) {
|
||||
|
@ -465,27 +485,27 @@ export default class AddressPickerDialog extends React.Component {
|
|||
address: query,
|
||||
isKnown: false,
|
||||
});
|
||||
if (this._cancelThreepidLookup) this._cancelThreepidLookup();
|
||||
if (this.cancelThreepidLookup) this.cancelThreepidLookup();
|
||||
if (addrType === 'email') {
|
||||
this._lookupThreepid(addrType, query);
|
||||
this.lookupThreepid(addrType, query);
|
||||
}
|
||||
}
|
||||
this.setState({
|
||||
suggestedList,
|
||||
invalidAddressError: false,
|
||||
}, () => {
|
||||
if (this.addressSelector) this.addressSelector.moveSelectionTop();
|
||||
if (this.addressSelector.current) this.addressSelector.current.moveSelectionTop();
|
||||
});
|
||||
}
|
||||
|
||||
_addAddressesToList(addressTexts) {
|
||||
private addAddressesToList(addressTexts: string[]): IUserAddress[] {
|
||||
const selectedList = this.state.selectedList.slice();
|
||||
|
||||
let hasError = false;
|
||||
addressTexts.forEach((addressText) => {
|
||||
addressText = addressText.trim();
|
||||
const addrType = getAddressType(addressText);
|
||||
const addrObj = {
|
||||
const addrObj: IUserAddress = {
|
||||
addressType: addrType,
|
||||
address: addressText,
|
||||
isKnown: false,
|
||||
|
@ -504,7 +524,6 @@ export default class AddressPickerDialog extends React.Component {
|
|||
const room = MatrixClientPeg.get().getRoom(addrObj.address);
|
||||
if (room) {
|
||||
addrObj.displayName = room.name;
|
||||
addrObj.avatarMxc = room.avatarUrl;
|
||||
addrObj.isKnown = true;
|
||||
}
|
||||
}
|
||||
|
@ -518,17 +537,17 @@ export default class AddressPickerDialog extends React.Component {
|
|||
query: "",
|
||||
invalidAddressError: hasError ? true : this.state.invalidAddressError,
|
||||
});
|
||||
if (this._cancelThreepidLookup) this._cancelThreepidLookup();
|
||||
if (this.cancelThreepidLookup) this.cancelThreepidLookup();
|
||||
return hasError ? null : selectedList;
|
||||
}
|
||||
|
||||
async _lookupThreepid(medium, address) {
|
||||
private async lookupThreepid(medium: AddressType, address: string): Promise<string> {
|
||||
let cancelled = false;
|
||||
// Note that we can't safely remove this after we're done
|
||||
// because we don't know that it's the same one, so we just
|
||||
// leave it: it's replacing the old one each time so it's
|
||||
// not like they leak.
|
||||
this._cancelThreepidLookup = function() {
|
||||
this.cancelThreepidLookup = function() {
|
||||
cancelled = true;
|
||||
};
|
||||
|
||||
|
@ -570,7 +589,7 @@ export default class AddressPickerDialog extends React.Component {
|
|||
}
|
||||
}
|
||||
|
||||
_getFilteredSuggestions() {
|
||||
private getFilteredSuggestions(): IUserAddress[] {
|
||||
// map addressType => set of addresses to avoid O(n*m) operation
|
||||
const selectedAddresses = {};
|
||||
this.state.selectedList.forEach(({ address, addressType }) => {
|
||||
|
@ -584,15 +603,15 @@ export default class AddressPickerDialog extends React.Component {
|
|||
});
|
||||
}
|
||||
|
||||
_onPaste = e => {
|
||||
private onPaste = (e: React.ClipboardEvent): void => {
|
||||
// Prevent the text being pasted into the textarea
|
||||
e.preventDefault();
|
||||
const text = e.clipboardData.getData("text");
|
||||
// Process it as a list of addresses to add instead
|
||||
this._addAddressesToList(text.split(/[\s,]+/));
|
||||
this.addAddressesToList(text.split(/[\s,]+/));
|
||||
};
|
||||
|
||||
onUseDefaultIdentityServerClick = e => {
|
||||
private onUseDefaultIdentityServerClick = (e: React.MouseEvent): void => {
|
||||
e.preventDefault();
|
||||
|
||||
// Update the IS in account data. Actually using it may trigger terms.
|
||||
|
@ -601,33 +620,27 @@ export default class AddressPickerDialog extends React.Component {
|
|||
|
||||
// Add email as a valid address type.
|
||||
const { validAddressTypes } = this.state;
|
||||
validAddressTypes.push('email');
|
||||
validAddressTypes.push(AddressType.Email);
|
||||
this.setState({ validAddressTypes });
|
||||
};
|
||||
|
||||
onManageSettingsClick = e => {
|
||||
private onManageSettingsClick = (e: React.MouseEvent): void => {
|
||||
e.preventDefault();
|
||||
dis.fire(Action.ViewUserSettings);
|
||||
this.onCancel();
|
||||
};
|
||||
|
||||
render() {
|
||||
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
|
||||
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
|
||||
const AddressSelector = sdk.getComponent("elements.AddressSelector");
|
||||
this.scrollElement = null;
|
||||
|
||||
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>;
|
||||
}
|
||||
|
||||
const query = [];
|
||||
// create the invite list
|
||||
if (this.state.selectedList.length > 0) {
|
||||
const AddressTile = sdk.getComponent("elements.AddressTile");
|
||||
for (let i = 0; i < this.state.selectedList.length; i++) {
|
||||
query.push(
|
||||
<AddressTile
|
||||
|
@ -644,19 +657,19 @@ export default class AddressPickerDialog extends React.Component {
|
|||
query.push(
|
||||
<textarea
|
||||
key={this.state.selectedList.length}
|
||||
onPaste={this._onPaste}
|
||||
rows="1"
|
||||
onPaste={this.onPaste}
|
||||
rows={1}
|
||||
id="textinput"
|
||||
ref={this._textinput}
|
||||
ref={this.textinput}
|
||||
className="mx_AddressPickerDialog_input"
|
||||
onChange={this.onQueryChanged}
|
||||
placeholder={this.getPlaceholder()}
|
||||
defaultValue={this.props.value}
|
||||
autoFocus={this.props.focus}>
|
||||
</textarea>,
|
||||
autoFocus={this.props.focus}
|
||||
/>,
|
||||
);
|
||||
|
||||
const filteredSuggestedList = this._getFilteredSuggestions();
|
||||
const filteredSuggestedList = this.getFilteredSuggestions();
|
||||
|
||||
let error;
|
||||
let addressSelector;
|
||||
|
@ -675,7 +688,7 @@ export default class AddressPickerDialog extends React.Component {
|
|||
error = <div className="mx_AddressPickerDialog_error">{ _t("No results") }</div>;
|
||||
} else {
|
||||
addressSelector = (
|
||||
<AddressSelector ref={(ref) => {this.addressSelector = ref;}}
|
||||
<AddressSelector ref={this.addressSelector}
|
||||
addressList={filteredSuggestedList}
|
||||
showAddress={this.props.pickerType === 'user'}
|
||||
onSelected={this.onSelected}
|
||||
|
@ -686,11 +699,11 @@ export default class AddressPickerDialog extends React.Component {
|
|||
|
||||
let identityServer;
|
||||
// If picker cannot currently accept e-mail but should be able to
|
||||
if (this.props.pickerType === 'user' && !this.state.validAddressTypes.includes('email')
|
||||
&& this.props.validAddressTypes.includes('email')) {
|
||||
if (this.props.pickerType === 'user' && !this.state.validAddressTypes.includes(AddressType.Email)
|
||||
&& this.props.validAddressTypes.includes(AddressType.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 +711,29 @@ 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}
|
||||
<BaseDialog
|
||||
className="mx_AddressPickerDialog"
|
||||
onKeyDown={this.onKeyDown}
|
||||
onFinished={this.props.onFinished}
|
||||
title={this.props.title}
|
||||
>
|
||||
{ inputLabel }
|
||||
<div className="mx_Dialog_content">
|
||||
<div className="mx_AddressPickerDialog_inputContainer">{ query }</div>
|
||||
{ error }
|
|
@ -51,7 +51,7 @@ export default class AskInviteAnywayDialog extends React.Component<IProps> {
|
|||
|
||||
public render() {
|
||||
const errorList = this.props.unknownProfileUsers
|
||||
.map(address => <li key={address.userId}>{address.userId}: {address.errorText}</li>);
|
||||
.map(address => <li key={address.userId}>{ address.userId }: { address.errorText }</li>);
|
||||
|
||||
return (
|
||||
<BaseDialog className='mx_RetryInvitesDialog'
|
||||
|
@ -60,8 +60,8 @@ export default class AskInviteAnywayDialog extends React.Component<IProps> {
|
|||
contentId='mx_Dialog_content'
|
||||
>
|
||||
<div id='mx_Dialog_content'>
|
||||
{/* eslint-disable-next-line */}
|
||||
<p>{_t("Unable to find profiles for the Matrix IDs listed below - would you like to invite them anyway?")}</p>
|
||||
<p>{ _t("Unable to find profiles for the Matrix IDs listed below - " +
|
||||
"would you like to invite them anyway?") }</p>
|
||||
<ul>
|
||||
{ errorList }
|
||||
</ul>
|
||||
|
|
|
@ -118,9 +118,7 @@ export default class BaseDialog extends React.Component {
|
|||
|
||||
let headerImage;
|
||||
if (this.props.headerImage) {
|
||||
headerImage = <img className="mx_Dialog_titleImage" src={this.props.headerImage}
|
||||
alt=""
|
||||
/>;
|
||||
headerImage = <img className="mx_Dialog_titleImage" src={this.props.headerImage} alt="" />;
|
||||
}
|
||||
|
||||
return (
|
||||
|
@ -149,7 +147,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 }
|
||||
|
|
|
@ -69,15 +69,18 @@ const BetaFeedbackDialog: React.FC<IProps> = ({ featureId, onFinished }) => {
|
|||
<div className="mx_BetaFeedbackDialog_subheading">
|
||||
{ _t(info.feedbackSubheading) }
|
||||
|
||||
{ _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);
|
||||
defaultDispatcher.dispatch({
|
||||
<AccessibleButton
|
||||
kind="link"
|
||||
onClick={() => {
|
||||
onFinished(false);
|
||||
defaultDispatcher.dispatch({
|
||||
action: Action.ViewUserSettings,
|
||||
initialTabId: UserTab.Labs,
|
||||
});
|
||||
}}>
|
||||
});
|
||||
}}
|
||||
>
|
||||
{ _t("To leave the beta, visit your settings.") }
|
||||
</AccessibleButton>
|
||||
</div>
|
||||
|
|
|
@ -166,7 +166,7 @@ export default class BugReportDialog extends React.Component<IProps, IState> {
|
|||
let error = null;
|
||||
if (this.state.err) {
|
||||
error = <div className="error">
|
||||
{this.state.err}
|
||||
{ this.state.err }
|
||||
</div>;
|
||||
}
|
||||
|
||||
|
@ -175,7 +175,7 @@ export default class BugReportDialog extends React.Component<IProps, IState> {
|
|||
progress = (
|
||||
<div className="progress">
|
||||
<Spinner />
|
||||
{this.state.progress} ...
|
||||
{ this.state.progress } ...
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -188,7 +188,9 @@ export default class BugReportDialog extends React.Component<IProps, IState> {
|
|||
}
|
||||
|
||||
return (
|
||||
<BaseDialog className="mx_BugReportDialog" onFinished={this.onCancel}
|
||||
<BaseDialog
|
||||
className="mx_BugReportDialog"
|
||||
onFinished={this.onCancel}
|
||||
title={_t('Submit debug logs')}
|
||||
contentId='mx_Dialog_content'
|
||||
>
|
||||
|
@ -221,7 +223,7 @@ export default class BugReportDialog extends React.Component<IProps, IState> {
|
|||
<AccessibleButton onClick={this.onDownload} kind="link" disabled={this.state.downloadBusy}>
|
||||
{ _t("Download logs") }
|
||||
</AccessibleButton>
|
||||
{this.state.downloadProgress && <span>{this.state.downloadProgress} ...</span>}
|
||||
{ this.state.downloadProgress && <span>{ this.state.downloadProgress } ...</span> }
|
||||
</div>
|
||||
|
||||
<Field
|
||||
|
@ -246,8 +248,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}
|
||||
|
|
|
@ -59,7 +59,7 @@ export default class ChangelogDialog extends React.Component<IProps> {
|
|||
return (
|
||||
<li key={commit.sha} className="mx_ChangelogDialog_li">
|
||||
<a href={commit.html_url} target="_blank" rel="noreferrer noopener">
|
||||
{commit.commit.message.split('\n')[0]}
|
||||
{ commit.commit.message.split('\n')[0] }
|
||||
</a>
|
||||
</li>
|
||||
);
|
||||
|
@ -79,15 +79,15 @@ export default class ChangelogDialog extends React.Component<IProps> {
|
|||
}
|
||||
return (
|
||||
<div key={repo}>
|
||||
<h2>{repo}</h2>
|
||||
<ul>{content}</ul>
|
||||
<h2>{ repo }</h2>
|
||||
<ul>{ content }</ul>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
const content = (
|
||||
<div className="mx_ChangelogDialog_content">
|
||||
{this.props.version == null || this.props.newVersion == null ? <h2>{_t("Unavailable")}</h2> : logs}
|
||||
{ this.props.version == null || this.props.newVersion == null ? <h2>{ _t("Unavailable") }</h2> : logs }
|
||||
</div>
|
||||
);
|
||||
|
||||
|
|
|
@ -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")}
|
||||
|
@ -205,18 +205,21 @@ export default class CommunityPrototypeInviteDialog extends React.PureComponent<
|
|||
people.push((
|
||||
<AccessibleButton
|
||||
onClick={this.onShowMorePeople}
|
||||
kind="link" key="more"
|
||||
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 +239,17 @@ 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}
|
||||
kind="primary"
|
||||
onClick={this.onSubmit}
|
||||
disabled={this.state.busy}
|
||||
className="mx_CommunityPrototypeInviteDialog_primaryButton"
|
||||
>{buttonText}</AccessibleButton>
|
||||
>
|
||||
{ buttonText }
|
||||
</AccessibleButton>
|
||||
</div>
|
||||
</form>
|
||||
</BaseDialog>
|
||||
|
|
|
@ -37,8 +37,8 @@ export default class ConfirmRedactDialog extends React.Component<IProps> {
|
|||
"Note that if you delete a room name or topic change, it could undo the change.")}
|
||||
placeholder={_t("Reason (optional)")}
|
||||
focus
|
||||
button={_t("Remove")}>
|
||||
</TextInputDialog>
|
||||
button={_t("Remove")}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -104,7 +104,9 @@ export default class ConfirmUserActionDialog extends React.Component<IProps> {
|
|||
}
|
||||
|
||||
return (
|
||||
<BaseDialog className="mx_ConfirmUserActionDialog" onFinished={this.props.onFinished}
|
||||
<BaseDialog
|
||||
className="mx_ConfirmUserActionDialog"
|
||||
onFinished={this.props.onFinished}
|
||||
title={this.props.title}
|
||||
contentId='mx_Dialog_content'
|
||||
>
|
||||
|
|
|
@ -44,10 +44,10 @@ export default class ConfirmWipeDeviceDialog extends React.Component<IProps> {
|
|||
>
|
||||
<div className='mx_ConfirmWipeDeviceDialog_content'>
|
||||
<p>
|
||||
{_t(
|
||||
{ _t(
|
||||
"Clearing all data from this session is permanent. Encrypted messages will be lost " +
|
||||
"unless their keys have been backed up.",
|
||||
)}
|
||||
) }
|
||||
</p>
|
||||
</div>
|
||||
<DialogButtons
|
||||
|
|
|
@ -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,31 +193,33 @@ 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*/}
|
||||
{communityId}
|
||||
{ /*nbsp is to reserve the height of this element when there's nothing*/ }
|
||||
{ communityId }
|
||||
</span>
|
||||
<AccessibleButton kind="primary" onClick={this.onSubmit} disabled={this.state.busy}>
|
||||
{_t("Create")}
|
||||
{ _t("Create") }
|
||||
</AccessibleButton>
|
||||
</div>
|
||||
<div className="mx_CreateCommunityPrototypeDialog_colAvatar">
|
||||
<input
|
||||
type="file" style={{ display: "none" }}
|
||||
ref={this.avatarUploadRef} accept="image/*"
|
||||
type="file"
|
||||
style={{ display: "none" }}
|
||||
ref={this.avatarUploadRef}
|
||||
accept="image/*"
|
||||
onChange={this.onAvatarChanged}
|
||||
/>
|
||||
<AccessibleButton
|
||||
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>
|
||||
|
|
|
@ -102,7 +102,7 @@ export default class CreateGroupDialog extends React.Component<IProps, IState> {
|
|||
});
|
||||
};
|
||||
|
||||
_onCancel = () => {
|
||||
private onCancel = () => {
|
||||
this.props.onFinished(false);
|
||||
};
|
||||
|
||||
|
@ -123,7 +123,9 @@ export default class CreateGroupDialog extends React.Component<IProps, IState> {
|
|||
}
|
||||
|
||||
return (
|
||||
<BaseDialog className="mx_CreateGroupDialog" onFinished={this.props.onFinished}
|
||||
<BaseDialog
|
||||
className="mx_CreateGroupDialog"
|
||||
onFinished={this.props.onFinished}
|
||||
title={_t('Create Community')}
|
||||
>
|
||||
<form onSubmit={this.onFormSubmit}>
|
||||
|
@ -133,8 +135,11 @@ export default class CreateGroupDialog extends React.Component<IProps, IState> {
|
|||
<label htmlFor="groupname">{ _t('Community Name') }</label>
|
||||
</div>
|
||||
<div>
|
||||
<input id="groupname" className="mx_CreateGroupDialog_input"
|
||||
autoFocus={true} size={64}
|
||||
<input
|
||||
id="groupname"
|
||||
className="mx_CreateGroupDialog_input"
|
||||
autoFocus={true}
|
||||
size={64}
|
||||
placeholder={_t('Example')}
|
||||
onChange={this.onGroupNameChange}
|
||||
value={this.state.groupName}
|
||||
|
@ -167,7 +172,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>
|
||||
|
|
|
@ -17,6 +17,7 @@ limitations under the License.
|
|||
|
||||
import React, { ChangeEvent, createRef, KeyboardEvent, SyntheticEvent } from "react";
|
||||
import { Room } from "matrix-js-sdk/src/models/room";
|
||||
import { JoinRule, Preset, Visibility } from "matrix-js-sdk/src/@types/partials";
|
||||
|
||||
import SdkConfig from '../../../SdkConfig';
|
||||
import withValidation, { IFieldState } from '../elements/Validation';
|
||||
|
@ -31,7 +32,8 @@ import RoomAliasField from "../elements/RoomAliasField";
|
|||
import LabelledToggleSwitch from "../elements/LabelledToggleSwitch";
|
||||
import DialogButtons from "../elements/DialogButtons";
|
||||
import BaseDialog from "../dialogs/BaseDialog";
|
||||
import { Preset, Visibility } from "matrix-js-sdk/src/@types/partials";
|
||||
import Dropdown from "../elements/Dropdown";
|
||||
import SpaceStore from "../../../stores/SpaceStore";
|
||||
|
||||
interface IProps {
|
||||
defaultPublic?: boolean;
|
||||
|
@ -41,7 +43,7 @@ interface IProps {
|
|||
}
|
||||
|
||||
interface IState {
|
||||
isPublic: boolean;
|
||||
joinRule: JoinRule;
|
||||
isEncrypted: boolean;
|
||||
name: string;
|
||||
topic: string;
|
||||
|
@ -54,15 +56,25 @@ interface IState {
|
|||
|
||||
@replaceableComponent("views.dialogs.CreateRoomDialog")
|
||||
export default class CreateRoomDialog extends React.Component<IProps, IState> {
|
||||
private readonly supportsRestricted: boolean;
|
||||
private nameField = createRef<Field>();
|
||||
private aliasField = createRef<RoomAliasField>();
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.supportsRestricted = this.props.parentSpace && !!SpaceStore.instance.restrictedJoinRuleSupport?.preferred;
|
||||
|
||||
let joinRule = JoinRule.Invite;
|
||||
if (this.props.defaultPublic) {
|
||||
joinRule = JoinRule.Public;
|
||||
} else if (this.supportsRestricted) {
|
||||
joinRule = JoinRule.Restricted;
|
||||
}
|
||||
|
||||
const config = SdkConfig.get();
|
||||
this.state = {
|
||||
isPublic: this.props.defaultPublic || false,
|
||||
joinRule,
|
||||
isEncrypted: privateShouldBeEncrypted(),
|
||||
name: this.props.defaultName || "",
|
||||
topic: "",
|
||||
|
@ -81,13 +93,18 @@ export default class CreateRoomDialog extends React.Component<IProps, IState> {
|
|||
const opts: IOpts = {};
|
||||
const createOpts: IOpts["createOpts"] = opts.createOpts = {};
|
||||
createOpts.name = this.state.name;
|
||||
if (this.state.isPublic) {
|
||||
|
||||
if (this.state.joinRule === JoinRule.Public) {
|
||||
createOpts.visibility = Visibility.Public;
|
||||
createOpts.preset = Preset.PublicChat;
|
||||
opts.guestAccess = false;
|
||||
const { alias } = this.state;
|
||||
createOpts.room_alias_name = alias.substr(1, alias.indexOf(":") - 1);
|
||||
} else {
|
||||
// If we cannot change encryption we pass `true` for safety, the server should automatically do this for us.
|
||||
opts.encryption = this.state.canChangeEncryption ? this.state.isEncrypted : true;
|
||||
}
|
||||
|
||||
if (this.state.topic) {
|
||||
createOpts.topic = this.state.topic;
|
||||
}
|
||||
|
@ -95,22 +112,13 @@ export default class CreateRoomDialog extends React.Component<IProps, IState> {
|
|||
createOpts.creation_content = { 'm.federate': false };
|
||||
}
|
||||
|
||||
if (!this.state.isPublic) {
|
||||
if (this.state.canChangeEncryption) {
|
||||
opts.encryption = this.state.isEncrypted;
|
||||
} else {
|
||||
// the server should automatically do this for us, but for safety
|
||||
// we'll demand it too.
|
||||
opts.encryption = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (CommunityPrototypeStore.instance.getSelectedCommunityId()) {
|
||||
opts.associatedWithCommunity = CommunityPrototypeStore.instance.getSelectedCommunityId();
|
||||
}
|
||||
|
||||
if (this.props.parentSpace) {
|
||||
if (this.props.parentSpace && this.state.joinRule === JoinRule.Restricted) {
|
||||
opts.parentSpace = this.props.parentSpace;
|
||||
opts.joinRule = JoinRule.Restricted;
|
||||
}
|
||||
|
||||
return opts;
|
||||
|
@ -172,8 +180,8 @@ export default class CreateRoomDialog extends React.Component<IProps, IState> {
|
|||
this.setState({ topic: ev.target.value });
|
||||
};
|
||||
|
||||
private onPublicChange = (isPublic: boolean) => {
|
||||
this.setState({ isPublic });
|
||||
private onJoinRuleChange = (joinRule: JoinRule) => {
|
||||
this.setState({ joinRule });
|
||||
};
|
||||
|
||||
private onEncryptedChange = (isEncrypted: boolean) => {
|
||||
|
@ -210,7 +218,7 @@ export default class CreateRoomDialog extends React.Component<IProps, IState> {
|
|||
|
||||
render() {
|
||||
let aliasField;
|
||||
if (this.state.isPublic) {
|
||||
if (this.state.joinRule === JoinRule.Public) {
|
||||
const domain = MatrixClientPeg.get().getDomain();
|
||||
aliasField = (
|
||||
<div className="mx_CreateRoomDialog_aliasContainer">
|
||||
|
@ -224,19 +232,46 @@ export default class CreateRoomDialog extends React.Component<IProps, IState> {
|
|||
);
|
||||
}
|
||||
|
||||
let publicPrivateLabel = <p>{_t(
|
||||
"Private rooms can be found and joined by invitation only. Public rooms can be " +
|
||||
"found and joined by anyone.",
|
||||
)}</p>;
|
||||
let publicPrivateLabel: JSX.Element;
|
||||
if (CommunityPrototypeStore.instance.getSelectedCommunityId()) {
|
||||
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>;
|
||||
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>;
|
||||
} else if (this.state.joinRule === JoinRule.Restricted) {
|
||||
publicPrivateLabel = <p>
|
||||
{ _t(
|
||||
"Everyone in <SpaceName/> will be able to find and join this room.", {}, {
|
||||
SpaceName: () => <b>{ this.props.parentSpace.name }</b>,
|
||||
},
|
||||
) }
|
||||
|
||||
{ _t("You can change this at any time from room settings.") }
|
||||
</p>;
|
||||
} else if (this.state.joinRule === JoinRule.Public) {
|
||||
publicPrivateLabel = <p>
|
||||
{ _t(
|
||||
"Anyone will be able to find and join this room, not just members of <SpaceName/>.", {}, {
|
||||
SpaceName: () => <b>{ this.props.parentSpace.name }</b>,
|
||||
},
|
||||
) }
|
||||
|
||||
{ _t("You can change this at any time from room settings.") }
|
||||
</p>;
|
||||
} else if (this.state.joinRule === JoinRule.Invite) {
|
||||
publicPrivateLabel = <p>
|
||||
{ _t(
|
||||
"Only people invited will be able to find and join this room.",
|
||||
) }
|
||||
|
||||
{ _t("You can change this at any time from room settings.") }
|
||||
</p>;
|
||||
}
|
||||
|
||||
let e2eeSection;
|
||||
if (!this.state.isPublic) {
|
||||
if (this.state.joinRule !== JoinRule.Public) {
|
||||
let microcopy;
|
||||
if (privateShouldBeEncrypted()) {
|
||||
if (this.state.canChangeEncryption) {
|
||||
|
@ -250,7 +285,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
|
||||
|
@ -273,15 +308,31 @@ export default class CreateRoomDialog extends React.Component<IProps, IState> {
|
|||
);
|
||||
}
|
||||
|
||||
let title = this.state.isPublic ? _t('Create a public room') : _t('Create a private room');
|
||||
let title = _t("Create a room");
|
||||
if (CommunityPrototypeStore.instance.getSelectedCommunityId()) {
|
||||
const name = CommunityPrototypeStore.instance.getSelectedCommunityName();
|
||||
title = _t("Create a room in %(communityName)s", { communityName: name });
|
||||
} else if (!this.props.parentSpace) {
|
||||
title = this.state.joinRule === JoinRule.Public ? _t('Create a public room') : _t('Create a private room');
|
||||
}
|
||||
|
||||
const options = [
|
||||
<div key={JoinRule.Invite} className="mx_CreateRoomDialog_dropdown_invite">
|
||||
{ _t("Private room (invite only)") }
|
||||
</div>,
|
||||
<div key={JoinRule.Public} className="mx_CreateRoomDialog_dropdown_public">
|
||||
{ _t("Public room") }
|
||||
</div>,
|
||||
];
|
||||
|
||||
if (this.supportsRestricted) {
|
||||
options.unshift(<div key={JoinRule.Restricted} className="mx_CreateRoomDialog_dropdown_restricted">
|
||||
{ _t("Visible to space members") }
|
||||
</div>);
|
||||
}
|
||||
|
||||
return (
|
||||
<BaseDialog className="mx_CreateRoomDialog" onFinished={this.props.onFinished}
|
||||
title={title}
|
||||
>
|
||||
<BaseDialog className="mx_CreateRoomDialog" onFinished={this.props.onFinished} title={title}>
|
||||
<form onSubmit={this.onOk} onKeyDown={this.onKeyDown}>
|
||||
<div className="mx_Dialog_content">
|
||||
<Field
|
||||
|
@ -298,11 +349,18 @@ export default class CreateRoomDialog extends React.Component<IProps, IState> {
|
|||
value={this.state.topic}
|
||||
className="mx_CreateRoomDialog_topic"
|
||||
/>
|
||||
<LabelledToggleSwitch
|
||||
label={_t("Make this room public")}
|
||||
onChange={this.onPublicChange}
|
||||
value={this.state.isPublic}
|
||||
/>
|
||||
|
||||
<Dropdown
|
||||
id="mx_CreateRoomDialog_typeDropdown"
|
||||
className="mx_CreateRoomDialog_typeDropdown"
|
||||
onOptionChange={this.onJoinRuleChange}
|
||||
menuWidth={448}
|
||||
value={this.state.joinRule}
|
||||
label={_t("Room visibility")}
|
||||
>
|
||||
{ options }
|
||||
</Dropdown>
|
||||
|
||||
{ publicPrivateLabel }
|
||||
{ e2eeSection }
|
||||
{ aliasField }
|
||||
|
@ -318,7 +376,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>
|
||||
|
|
|
@ -72,7 +72,7 @@ const CryptoStoreTooNewDialog: React.FC<IProps> = (props: IProps) => {
|
|||
hasCancel={false}
|
||||
onPrimaryButtonClick={props.onFinished}
|
||||
>
|
||||
<button onClick={_onLogoutClicked} >
|
||||
<button onClick={_onLogoutClicked}>
|
||||
{ _t('Sign out') }
|
||||
</button>
|
||||
</DialogButtons>
|
||||
|
|
|
@ -172,11 +172,11 @@ export default class DeactivateAccountDialog extends React.Component<IProps, ISt
|
|||
</div>;
|
||||
}
|
||||
|
||||
let auth = <div>{_t("Loading...")}</div>;
|
||||
let auth = <div>{ _t("Loading...") }</div>;
|
||||
if (this.state.authData && this.state.authEnabled) {
|
||||
auth = (
|
||||
<div>
|
||||
{this.state.bodyText}
|
||||
{ this.state.bodyText }
|
||||
<InteractiveAuth
|
||||
matrixClient={MatrixClientPeg.get()}
|
||||
authData={this.state.authData}
|
||||
|
@ -230,18 +230,18 @@ export default class DeactivateAccountDialog extends React.Component<IProps, ISt
|
|||
checked={this.state.shouldErase}
|
||||
onChange={this.onEraseFieldChange}
|
||||
>
|
||||
{_t(
|
||||
{ _t(
|
||||
"Please forget all messages I have sent when my account is deactivated " +
|
||||
"(<b>Warning:</b> this will cause future users to see an incomplete view " +
|
||||
"of conversations)",
|
||||
{},
|
||||
{ b: (sub) => <b>{ sub }</b> },
|
||||
)}
|
||||
) }
|
||||
</StyledCheckbox>
|
||||
</p>
|
||||
|
||||
{error}
|
||||
{auth}
|
||||
{ error }
|
||||
{ auth }
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
|
|
@ -182,14 +182,23 @@ export class SendCustomEvent extends GenericEditor<ISendCustomEventProps, ISendC
|
|||
|
||||
<br />
|
||||
|
||||
<Field id="evContent" label={_t("Event Content")} type="text" className="mx_DevTools_textarea"
|
||||
autoComplete="off" value={this.state.evContent} onChange={this.onChange} element="textarea" />
|
||||
<Field
|
||||
id="evContent"
|
||||
label={_t("Event Content")}
|
||||
type="text"
|
||||
className="mx_DevTools_textarea"
|
||||
autoComplete="off"
|
||||
value={this.state.evContent}
|
||||
onChange={this.onChange}
|
||||
element="textarea" />
|
||||
</div>
|
||||
<div className="mx_Dialog_buttons">
|
||||
<button onClick={this.onBack}>{ _t('Back') }</button>
|
||||
{ !this.state.message && <button onClick={this.send}>{ _t('Send') }</button> }
|
||||
{ showTglFlip && <div style={{ float: "right" }}>
|
||||
<input id="isStateEvent" className="mx_DevTools_tgl mx_DevTools_tgl-flip"
|
||||
<input
|
||||
id="isStateEvent"
|
||||
className="mx_DevTools_tgl mx_DevTools_tgl-flip"
|
||||
type="checkbox"
|
||||
checked={this.state.isStateEvent}
|
||||
onChange={this.onChange}
|
||||
|
@ -282,14 +291,24 @@ class SendAccountData extends GenericEditor<ISendAccountDataProps, ISendAccountD
|
|||
{ this.textInput('eventType', _t('Event Type')) }
|
||||
<br />
|
||||
|
||||
<Field id="evContent" label={_t("Event Content")} type="text" className="mx_DevTools_textarea"
|
||||
autoComplete="off" value={this.state.evContent} onChange={this.onChange} element="textarea" />
|
||||
<Field
|
||||
id="evContent"
|
||||
label={_t("Event Content")}
|
||||
type="text"
|
||||
className="mx_DevTools_textarea"
|
||||
autoComplete="off"
|
||||
value={this.state.evContent}
|
||||
onChange={this.onChange}
|
||||
element="textarea"
|
||||
/>
|
||||
</div>
|
||||
<div className="mx_Dialog_buttons">
|
||||
<button onClick={this.onBack}>{ _t('Back') }</button>
|
||||
{ !this.state.message && <button onClick={this.send}>{ _t('Send') }</button> }
|
||||
{ !this.state.message && <div style={{ float: "right" }}>
|
||||
<input id="isRoomAccountData" className="mx_DevTools_tgl mx_DevTools_tgl-flip"
|
||||
<input
|
||||
id="isRoomAccountData"
|
||||
className="mx_DevTools_tgl mx_DevTools_tgl-flip"
|
||||
type="checkbox"
|
||||
checked={this.state.isRoomAccountData}
|
||||
disabled={this.props.forceMode}
|
||||
|
@ -337,7 +356,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),
|
||||
|
@ -371,11 +390,18 @@ class FilteredList extends React.PureComponent<IFilteredListProps, IFilteredList
|
|||
|
||||
render() {
|
||||
return <div>
|
||||
<Field label={_t('Filter results')} autoFocus={true} size={64}
|
||||
type="text" autoComplete="off" value={this.props.query} onChange={this.onQuery}
|
||||
<Field
|
||||
label={_t('Filter results')}
|
||||
autoFocus={true}
|
||||
size={64}
|
||||
type="text"
|
||||
autoComplete="off"
|
||||
value={this.props.query}
|
||||
onChange={this.onQuery}
|
||||
className="mx_TextInputDialog_input mx_DevTools_RoomStateExplorer_query"
|
||||
// force re-render so that autoFocus is applied when this component is re-used
|
||||
key={this.props.children[0] ? this.props.children[0].key : ''} />
|
||||
key={this.props.children[0] ? this.props.children[0].key : ''}
|
||||
/>
|
||||
|
||||
<TruncatedList getChildren={this.getChildren}
|
||||
getChildCount={this.getChildCount}
|
||||
|
@ -459,11 +485,16 @@ class RoomStateExplorer extends React.PureComponent<IExplorerProps, IRoomStateEx
|
|||
render() {
|
||||
if (this.state.event) {
|
||||
if (this.state.editing) {
|
||||
return <SendCustomEvent room={this.props.room} forceStateEvent={true} onBack={this.onBack} inputs={{
|
||||
return <SendCustomEvent
|
||||
room={this.props.room}
|
||||
forceStateEvent={true}
|
||||
onBack={this.onBack}
|
||||
inputs={{
|
||||
eventType: this.state.event.getType(),
|
||||
evContent: JSON.stringify(this.state.event.getContent(), null, '\t'),
|
||||
stateKey: this.state.event.getStateKey(),
|
||||
}} />;
|
||||
}}
|
||||
/>;
|
||||
}
|
||||
|
||||
return <div className="mx_ViewSource">
|
||||
|
@ -494,7 +525,7 @@ class RoomStateExplorer extends React.PureComponent<IExplorerProps, IRoomStateEx
|
|||
}
|
||||
|
||||
return <button className={classes} key={eventType} onClick={onClickFn}>
|
||||
{eventType}
|
||||
{ eventType }
|
||||
</button>;
|
||||
})
|
||||
}
|
||||
|
@ -594,7 +625,9 @@ class AccountDataExplorer extends React.PureComponent<IExplorerProps, IAccountDa
|
|||
inputs={{
|
||||
eventType: this.state.event.getType(),
|
||||
evContent: JSON.stringify(this.state.event.getContent(), null, '\t'),
|
||||
}} forceMode={true} />;
|
||||
}}
|
||||
forceMode={true}
|
||||
/>;
|
||||
}
|
||||
|
||||
return <div className="mx_ViewSource">
|
||||
|
@ -631,7 +664,9 @@ class AccountDataExplorer extends React.PureComponent<IExplorerProps, IAccountDa
|
|||
<div className="mx_Dialog_buttons">
|
||||
<button onClick={this.onBack}>{ _t('Back') }</button>
|
||||
<div style={{ float: "right" }}>
|
||||
<input id="isRoomAccountData" className="mx_DevTools_tgl mx_DevTools_tgl-flip"
|
||||
<input
|
||||
id="isRoomAccountData"
|
||||
className="mx_DevTools_tgl mx_DevTools_tgl-flip"
|
||||
type="checkbox"
|
||||
checked={this.state.isRoomAccountData}
|
||||
onChange={this.onChange}
|
||||
|
@ -726,17 +761,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 +806,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 +879,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 +900,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 +1042,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() {
|
||||
|
@ -1021,46 +1056,53 @@ class SettingsExplorer extends React.PureComponent<IExplorerProps, ISettingsExpl
|
|||
<div>
|
||||
<div className="mx_Dialog_content mx_DevTools_SettingsExplorer">
|
||||
<Field
|
||||
label={_t('Filter results')} autoFocus={true} size={64}
|
||||
type="text" autoComplete="off" value={this.state.query} onChange={this.onQueryChange}
|
||||
label={_t('Filter results')}
|
||||
autoFocus={true}
|
||||
size={64}
|
||||
type="text"
|
||||
autoComplete="off"
|
||||
value={this.state.query}
|
||||
onChange={this.onQueryChange}
|
||||
className="mx_TextInputDialog_input mx_DevTools_RoomStateExplorer_query"
|
||||
/>
|
||||
<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)}
|
||||
<a
|
||||
href=""
|
||||
onClick={(e) => this.onEditClick(e, i)}
|
||||
className='mx_DevTools_SettingsExplorer_edit'
|
||||
>
|
||||
✏
|
||||
</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,62 +1110,70 @@ 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>
|
||||
|
||||
<div>
|
||||
<Field
|
||||
id="valExpl" label={_t("Values at explicit levels")} type="text"
|
||||
className="mx_DevTools_textarea" element="textarea"
|
||||
autoComplete="off" value={this.state.explicitValues}
|
||||
id="valExpl"
|
||||
label={_t("Values at explicit levels")}
|
||||
type="text"
|
||||
className="mx_DevTools_textarea"
|
||||
element="textarea"
|
||||
autoComplete="off"
|
||||
value={this.state.explicitValues}
|
||||
onChange={this.onExplValuesEdit}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Field
|
||||
id="valExpl" label={_t("Values at explicit levels in this room")} type="text"
|
||||
className="mx_DevTools_textarea" element="textarea"
|
||||
autoComplete="off" value={this.state.explicitRoomValues}
|
||||
id="valExpl"
|
||||
label={_t("Values at explicit levels in this room")}
|
||||
type="text"
|
||||
className="mx_DevTools_textarea"
|
||||
element="textarea"
|
||||
autoComplete="off"
|
||||
value={this.state.explicitRoomValues}
|
||||
onChange={this.onExplRoomValuesEdit}
|
||||
/>
|
||||
</div>
|
||||
|
||||
</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 +1181,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:")}
|
||||
<code>{this.renderSettingValue(
|
||||
{ _t("Value:") }
|
||||
<code>{ this.renderSettingValue(
|
||||
SettingsStore.getValue(this.state.viewSetting),
|
||||
)}</code>
|
||||
) }</code>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
{_t("Value in this room:")}
|
||||
<code>{this.renderSettingValue(
|
||||
{ _t("Value in this room:") }
|
||||
<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 +1221,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 +1282,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";
|
||||
|
|
|
@ -144,23 +144,25 @@ export default class EditCommunityPrototypeDialog extends React.PureComponent<IP
|
|||
</div>
|
||||
<div className="mx_EditCommunityPrototypeDialog_rowAvatar">
|
||||
<input
|
||||
type="file" style={{ display: "none" }}
|
||||
ref={this.avatarUploadRef} accept="image/*"
|
||||
type="file"
|
||||
style={{ display: "none" }}
|
||||
ref={this.avatarUploadRef}
|
||||
accept="image/*"
|
||||
onChange={this.onAvatarChanged}
|
||||
/>
|
||||
<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>
|
||||
|
|
|
@ -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>}
|
||||
|
|
|
@ -106,12 +106,12 @@ const Entry: React.FC<IEntryProps> = ({ room, event, matrixClient: cli, onFinish
|
|||
className = "mx_ForwardList_sending";
|
||||
disabled = true;
|
||||
title = _t("Sending");
|
||||
icon = <div className="mx_ForwardList_sendIcon" aria-label={title}></div>;
|
||||
icon = <div className="mx_ForwardList_sendIcon" aria-label={title} />;
|
||||
} else if (sendState === SendState.Sent) {
|
||||
className = "mx_ForwardList_sent";
|
||||
disabled = true;
|
||||
title = _t("Sent");
|
||||
icon = <div className="mx_ForwardList_sendIcon" aria-label={title}></div>;
|
||||
icon = <div className="mx_ForwardList_sendIcon" aria-label={title} />;
|
||||
} else {
|
||||
className = "mx_ForwardList_sendFailed";
|
||||
disabled = true;
|
||||
|
@ -204,10 +204,16 @@ const ForwardDialog: React.FC<IProps> = ({ matrixClient: cli, event, permalinkCr
|
|||
function overflowTile(overflowCount, totalCount) {
|
||||
const text = _t("and %(count)s others...", { count: overflowCount });
|
||||
return (
|
||||
<EntityTile className="mx_EntityTile_ellipsis" avatarJsx={
|
||||
<BaseAvatar url={require("../../../../res/img/ellipsis.svg")} name="..." width={36} height={36} />
|
||||
} name={text} presenceState="online" suppressOnHover={true}
|
||||
onClick={() => setTruncateAt(totalCount)} />
|
||||
<EntityTile
|
||||
className="mx_EntityTile_ellipsis"
|
||||
avatarJsx={
|
||||
<BaseAvatar url={require("../../../../res/img/ellipsis.svg")} name="..." width={36} height={36} />
|
||||
}
|
||||
name={text}
|
||||
presenceState="online"
|
||||
suppressOnHover={true}
|
||||
onClick={() => setTruncateAt(totalCount)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -133,55 +133,60 @@ export default class IncomingSasDialog extends React.Component {
|
|||
? mediaFromMxc(oppProfile.avatar_url).getSquareThumbnailHttp(48)
|
||||
: null;
|
||||
profile = <div className="mx_IncomingSasDialog_opponentProfile">
|
||||
<BaseAvatar name={oppProfile.displayname}
|
||||
<BaseAvatar
|
||||
name={oppProfile.displayname}
|
||||
idName={this.props.verifier.userId}
|
||||
url={url}
|
||||
width={48} height={48} resizeMethod='crop'
|
||||
width={48}
|
||||
height={48}
|
||||
resizeMethod='crop'
|
||||
/>
|
||||
<h2>{oppProfile.displayname}</h2>
|
||||
<h2>{ oppProfile.displayname }</h2>
|
||||
</div>;
|
||||
} else if (this.state.opponentProfileError) {
|
||||
profile = <div>
|
||||
<BaseAvatar name={this.props.verifier.userId.slice(1)}
|
||||
<BaseAvatar
|
||||
name={this.props.verifier.userId.slice(1)}
|
||||
idName={this.props.verifier.userId}
|
||||
width={48} height={48}
|
||||
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 +214,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 +256,7 @@ export default class IncomingSasDialog extends React.Component {
|
|||
onFinished={this._onFinished}
|
||||
fixedWidth={false}
|
||||
>
|
||||
{body}
|
||||
{ body }
|
||||
</BaseDialog>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
/*
|
||||
Copyright 2015, 2016 OpenMarket Ltd
|
||||
Copyright 2017 New Vector Ltd.
|
||||
Copyright 2019 Bastian Masanek, Noxware IT <matrix@noxware.de>
|
||||
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,31 +15,31 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import * as sdk from '../../../index';
|
||||
import { _t } from '../../../languageHandler';
|
||||
import React, { ReactNode, KeyboardEvent } from 'react';
|
||||
import classNames from "classnames";
|
||||
|
||||
export default class InfoDialog extends React.Component {
|
||||
static propTypes = {
|
||||
className: PropTypes.string,
|
||||
title: PropTypes.string,
|
||||
description: PropTypes.node,
|
||||
button: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]),
|
||||
onFinished: PropTypes.func,
|
||||
hasCloseButton: PropTypes.bool,
|
||||
onKeyDown: PropTypes.func,
|
||||
fixedWidth: PropTypes.bool,
|
||||
};
|
||||
import { _t } from '../../../languageHandler';
|
||||
import * as sdk from '../../../index';
|
||||
import { IDialogProps } from "./IDialogProps";
|
||||
|
||||
interface IProps extends IDialogProps {
|
||||
title?: string;
|
||||
description?: ReactNode;
|
||||
className?: string;
|
||||
button?: boolean | string;
|
||||
hasCloseButton?: boolean;
|
||||
fixedWidth?: boolean;
|
||||
onKeyDown?(event: KeyboardEvent): void;
|
||||
}
|
||||
|
||||
export default class InfoDialog extends React.Component<IProps> {
|
||||
static defaultProps = {
|
||||
title: '',
|
||||
description: '',
|
||||
hasCloseButton: false,
|
||||
};
|
||||
|
||||
onFinished = () => {
|
||||
private onFinished = () => {
|
||||
this.props.onFinished();
|
||||
};
|
||||
|
||||
|
@ -63,8 +62,7 @@ export default class InfoDialog extends React.Component {
|
|||
{ this.props.button !== false && <DialogButtons primaryButton={this.props.button || _t('OK')}
|
||||
onPrimaryButtonClick={this.onFinished}
|
||||
hasCancel={false}
|
||||
>
|
||||
</DialogButtons> }
|
||||
/> }
|
||||
</BaseDialog>
|
||||
);
|
||||
}
|
|
@ -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")}
|
||||
|
|
|
@ -45,11 +45,11 @@ export default class IntegrationsImpossibleDialog extends React.Component {
|
|||
>
|
||||
<div className='mx_IntegrationsImpossibleDialog_content'>
|
||||
<p>
|
||||
{_t(
|
||||
{ _t(
|
||||
"Your %(brand)s doesn't allow you to use an integration manager to do this. " +
|
||||
"Please contact an admin.",
|
||||
{ brand },
|
||||
)}
|
||||
) }
|
||||
</p>
|
||||
</div>
|
||||
<DialogButtons
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -196,7 +196,9 @@ class DMUserTile extends React.PureComponent<IDMUserTileProps> {
|
|||
? <img
|
||||
className='mx_InviteDialog_userTile_avatar mx_InviteDialog_userTile_threepidAvatar'
|
||||
src={require("../../../../res/img/icon-email-pill-avatar.svg")}
|
||||
width={avatarSize} height={avatarSize} />
|
||||
width={avatarSize}
|
||||
height={avatarSize}
|
||||
/>
|
||||
: <BaseAvatar
|
||||
className='mx_InviteDialog_userTile_avatar'
|
||||
url={this.props.member.getMxcAvatarUrl()
|
||||
|
@ -214,8 +216,11 @@ class DMUserTile extends React.PureComponent<IDMUserTileProps> {
|
|||
className='mx_InviteDialog_userTile_remove'
|
||||
onClick={this.onRemove}
|
||||
>
|
||||
<img src={require("../../../../res/img/icon-pill-remove.svg")}
|
||||
alt={_t('Remove')} width={8} height={8}
|
||||
<img
|
||||
src={require("../../../../res/img/icon-pill-remove.svg")}
|
||||
alt={_t('Remove')}
|
||||
width={8}
|
||||
height={8}
|
||||
/>
|
||||
</AccessibleButton>
|
||||
);
|
||||
|
@ -224,8 +229,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>
|
||||
|
@ -267,20 +272,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;
|
||||
|
@ -290,14 +295,16 @@ 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;
|
||||
const avatar = (this.props.member as ThreepidMember).isEmail
|
||||
? <img
|
||||
src={require("../../../../res/img/icon-email-pill-avatar.svg")}
|
||||
width={avatarSize} height={avatarSize} />
|
||||
width={avatarSize}
|
||||
height={avatarSize}
|
||||
/>
|
||||
: <BaseAvatar
|
||||
url={this.props.member.getMxcAvatarUrl()
|
||||
? mediaFromMxc(this.props.member.getMxcAvatarUrl()).getSquareThumbnailHttp(avatarSize)
|
||||
|
@ -317,8 +324,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>
|
||||
);
|
||||
|
||||
|
@ -328,12 +335,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>
|
||||
);
|
||||
}
|
||||
|
@ -1152,8 +1159,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>
|
||||
);
|
||||
}
|
||||
|
@ -1175,7 +1182,7 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
|
|||
if (hasMore) {
|
||||
showMore = (
|
||||
<AccessibleButton onClick={showMoreFn} kind="link">
|
||||
{_t("Show more")}
|
||||
{ _t("Show more") }
|
||||
</AccessibleButton>
|
||||
);
|
||||
}
|
||||
|
@ -1192,10 +1199,10 @@ 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>
|
||||
);
|
||||
}
|
||||
|
@ -1225,8 +1232,8 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
|
|||
);
|
||||
return (
|
||||
<div className='mx_InviteDialog_editor' onClick={this.onClickInputArea}>
|
||||
{targets}
|
||||
{input}
|
||||
{ targets }
|
||||
{ input }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -1241,7 +1248,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>.",
|
||||
|
@ -1249,20 +1256,20 @@ 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>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1339,7 +1346,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>
|
||||
);
|
||||
} },
|
||||
);
|
||||
|
@ -1349,7 +1356,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>
|
||||
);
|
||||
} },
|
||||
);
|
||||
|
@ -1367,7 +1374,7 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
|
|||
href={makeUserPermalink(userId)}
|
||||
rel="noreferrer noopener"
|
||||
target="_blank"
|
||||
>{userId}</a>
|
||||
>{ userId }</a>
|
||||
);
|
||||
},
|
||||
a: (sub) => {
|
||||
|
@ -1375,13 +1382,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");
|
||||
|
@ -1438,9 +1445,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");
|
||||
|
@ -1458,8 +1465,9 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
|
|||
<p className='mx_InviteDialog_helpText'>
|
||||
<img
|
||||
src={require("../../../../res/img/element-icons/info.svg")}
|
||||
width={14} height={14} />
|
||||
{" " + _t("Invited people will be able to read old messages.")}
|
||||
width={14}
|
||||
height={14} />
|
||||
{ " " + _t("Invited people will be able to read old messages.") }
|
||||
</p>;
|
||||
}
|
||||
}
|
||||
|
@ -1469,14 +1477,14 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
|
|||
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")}
|
||||
{ _t("Cancel") }
|
||||
</AccessibleButton>
|
||||
<AccessibleButton
|
||||
kind="primary"
|
||||
|
@ -1484,7 +1492,7 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
|
|||
className='mx_InviteDialog_transferButton'
|
||||
disabled={!hasSelection && this.state.dialPadValue === ''}
|
||||
>
|
||||
{_t("Transfer")}
|
||||
{ _t("Transfer") }
|
||||
</AccessibleButton>
|
||||
</div>;
|
||||
} else {
|
||||
|
@ -1497,27 +1505,27 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
|
|||
className='mx_InviteDialog_goButton'
|
||||
disabled={this.state.busy || !hasSelection}
|
||||
>
|
||||
{buttonText}
|
||||
{ buttonText }
|
||||
</AccessibleButton>;
|
||||
|
||||
const usersSection = <React.Fragment>
|
||||
<p className='mx_InviteDialog_helpText'>{helpText}</p>
|
||||
<p className='mx_InviteDialog_helpText'>{ helpText }</p>
|
||||
<div className='mx_InviteDialog_addressBar'>
|
||||
{this.renderEditor()}
|
||||
{ this.renderEditor() }
|
||||
<div className='mx_InviteDialog_buttonAndSpinner'>
|
||||
{goButton}
|
||||
{spinner}
|
||||
{ goButton }
|
||||
{ spinner }
|
||||
</div>
|
||||
</div>
|
||||
{keySharingWarning}
|
||||
{this.renderIdentityServerWarning()}
|
||||
<div className='error'>{this.state.errorText}</div>
|
||||
{ keySharingWarning }
|
||||
{ this.renderIdentityServerWarning() }
|
||||
<div className='error'>{ this.state.errorText }</div>
|
||||
<div className='mx_InviteDialog_userSections'>
|
||||
{this.renderSection('recents')}
|
||||
{this.renderSection('suggestions')}
|
||||
{extraSection}
|
||||
{ this.renderSection('recents') }
|
||||
{ this.renderSection('suggestions') }
|
||||
{ extraSection }
|
||||
</div>
|
||||
{footer}
|
||||
{ footer }
|
||||
</React.Fragment>;
|
||||
|
||||
let dialogContent;
|
||||
|
@ -1534,14 +1542,18 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
|
|||
// 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"
|
||||
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"
|
||||
dialPadField = <Field
|
||||
className="mx_InviteDialog_dialPadField"
|
||||
id="dialpad_number"
|
||||
value={this.state.dialPadValue}
|
||||
autoFocus={true}
|
||||
onChange={this.onDialChange}
|
||||
|
@ -1550,23 +1562,28 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
|
|||
|
||||
const dialPadSection = <div className="mx_InviteDialog_dialPad">
|
||||
<form onSubmit={this.onDialFormSubmit}>
|
||||
{dialPadField}
|
||||
{ dialPadField }
|
||||
</form>
|
||||
<Dialpad hasDial={false}
|
||||
onDigitPress={this.onDigitPress} onDeletePress={this.onDeletePress}
|
||||
<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}
|
||||
<TabbedView
|
||||
tabs={tabs}
|
||||
initialTabId={this.state.currentTabId}
|
||||
tabLocation={TabLocation.TOP}
|
||||
onChange={this.onTabChange}
|
||||
/>
|
||||
{consultConnectSection}
|
||||
{ consultConnectSection }
|
||||
</React.Fragment>;
|
||||
} else {
|
||||
dialogContent = <React.Fragment>
|
||||
{usersSection}
|
||||
{consultConnectSection}
|
||||
{ usersSection }
|
||||
{ consultConnectSection }
|
||||
</React.Fragment>;
|
||||
}
|
||||
|
||||
|
@ -1582,7 +1599,7 @@ export default class InviteDialog extends React.PureComponent<IInviteDialogProps
|
|||
title={title}
|
||||
>
|
||||
<div className='mx_InviteDialog_content'>
|
||||
{dialogContent}
|
||||
{ dialogContent }
|
||||
</div>
|
||||
</BaseDialog>
|
||||
);
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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}
|
||||
/>);
|
||||
|
|
|
@ -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}
|
||||
/>);
|
||||
|
|
|
@ -16,12 +16,12 @@ limitations under the License.
|
|||
|
||||
import React, { useEffect, useMemo, useState } from "react";
|
||||
import { Room } from "matrix-js-sdk/src/models/room";
|
||||
import { JoinRule } from "matrix-js-sdk/src/@types/partials";
|
||||
|
||||
import { _t } from '../../../languageHandler';
|
||||
import Dropdown from "../elements/Dropdown";
|
||||
import DialogButtons from "../elements/DialogButtons";
|
||||
import BaseDialog from "../dialogs/BaseDialog";
|
||||
import { JoinRule } from "../settings/tabs/room/SecurityRoomSettingsTab";
|
||||
import SpaceStore from "../../../stores/SpaceStore";
|
||||
import AutoHideScrollbar from "../../structures/AutoHideScrollbar";
|
||||
import { Entry } from "./AddExistingToSpaceDialog";
|
||||
|
@ -81,7 +81,7 @@ const SpaceChildPicker = ({ filterPlaceholder, rooms, selected, onChange }) => {
|
|||
|
||||
const LeaveRoomsPicker = ({ space, spaceChildren, roomsToLeave, setRoomsToLeave }) => {
|
||||
const selected = useMemo(() => new Set(roomsToLeave), [roomsToLeave]);
|
||||
const [state, setState] = useState<RoomsToLeave>(RoomsToLeave.All);
|
||||
const [state, setState] = useState<string>(RoomsToLeave.All);
|
||||
|
||||
useEffect(() => {
|
||||
if (state === RoomsToLeave.All) {
|
||||
|
|
|
@ -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');
|
||||
|
|
192
src/components/views/dialogs/ManageRestrictedJoinRuleDialog.tsx
Normal file
192
src/components/views/dialogs/ManageRestrictedJoinRuleDialog.tsx
Normal file
|
@ -0,0 +1,192 @@
|
|||
/*
|
||||
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, { useMemo, useState } from "react";
|
||||
import { Room } from "matrix-js-sdk/src/models/room";
|
||||
|
||||
import { _t } from '../../../languageHandler';
|
||||
import { IDialogProps } from "./IDialogProps";
|
||||
import BaseDialog from "./BaseDialog";
|
||||
import SearchBox from "../../structures/SearchBox";
|
||||
import SpaceStore from "../../../stores/SpaceStore";
|
||||
import RoomAvatar from "../avatars/RoomAvatar";
|
||||
import AccessibleButton from "../elements/AccessibleButton";
|
||||
import AutoHideScrollbar from "../../structures/AutoHideScrollbar";
|
||||
import StyledCheckbox from "../elements/StyledCheckbox";
|
||||
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
||||
|
||||
interface IProps extends IDialogProps {
|
||||
room: Room;
|
||||
selected?: string[];
|
||||
}
|
||||
|
||||
const Entry = ({ room, checked, onChange }) => {
|
||||
const localRoom = room instanceof Room;
|
||||
|
||||
let description;
|
||||
if (localRoom) {
|
||||
description = _t("%(count)s members", { count: room.getJoinedMemberCount() });
|
||||
const numChildRooms = SpaceStore.instance.getChildRooms(room.roomId).length;
|
||||
if (numChildRooms > 0) {
|
||||
description += " · " + _t("%(count)s rooms", { count: numChildRooms });
|
||||
}
|
||||
}
|
||||
|
||||
return <label className="mx_ManageRestrictedJoinRuleDialog_entry">
|
||||
<div>
|
||||
<div>
|
||||
{ localRoom
|
||||
? <RoomAvatar room={room} height={20} width={20} />
|
||||
: <RoomAvatar oobData={room} height={20} width={20} />
|
||||
}
|
||||
<span className="mx_ManageRestrictedJoinRuleDialog_entry_name">{ room.name }</span>
|
||||
</div>
|
||||
{ description && <div className="mx_ManageRestrictedJoinRuleDialog_entry_description">
|
||||
{ description }
|
||||
</div> }
|
||||
</div>
|
||||
<StyledCheckbox
|
||||
onChange={onChange ? (e) => onChange(e.target.checked) : null}
|
||||
checked={checked}
|
||||
disabled={!onChange}
|
||||
/>
|
||||
</label>;
|
||||
};
|
||||
|
||||
const ManageRestrictedJoinRuleDialog: React.FC<IProps> = ({ room, selected = [], onFinished }) => {
|
||||
const cli = room.client;
|
||||
const [newSelected, setNewSelected] = useState(new Set<string>(selected));
|
||||
const [query, setQuery] = useState("");
|
||||
const lcQuery = query.toLowerCase().trim();
|
||||
|
||||
const [spacesContainingRoom, otherEntries] = useMemo(() => {
|
||||
const spaces = cli.getVisibleRooms().filter(r => r.getMyMembership() === "join" && r.isSpaceRoom());
|
||||
return [
|
||||
spaces.filter(r => SpaceStore.instance.getSpaceFilteredRoomIds(r).has(room.roomId)),
|
||||
selected.map(roomId => {
|
||||
const room = cli.getRoom(roomId);
|
||||
if (!room) {
|
||||
return { roomId, name: roomId } as Room;
|
||||
}
|
||||
if (room.getMyMembership() !== "join" || !room.isSpaceRoom()) {
|
||||
return room;
|
||||
}
|
||||
}).filter(Boolean),
|
||||
];
|
||||
}, [cli, selected, room.roomId]);
|
||||
|
||||
const [filteredSpacesContainingRooms, filteredOtherEntries] = useMemo(() => [
|
||||
spacesContainingRoom.filter(r => r.name.toLowerCase().includes(lcQuery)),
|
||||
otherEntries.filter(r => r.name.toLowerCase().includes(lcQuery)),
|
||||
], [spacesContainingRoom, otherEntries, lcQuery]);
|
||||
|
||||
const onChange = (checked: boolean, room: Room): void => {
|
||||
if (checked) {
|
||||
newSelected.add(room.roomId);
|
||||
} else {
|
||||
newSelected.delete(room.roomId);
|
||||
}
|
||||
setNewSelected(new Set(newSelected));
|
||||
};
|
||||
|
||||
let inviteOnlyWarning;
|
||||
if (newSelected.size < 1) {
|
||||
inviteOnlyWarning = <div className="mx_ManageRestrictedJoinRuleDialog_section_info">
|
||||
{ _t("You're removing all spaces. Access will default to invite only") }
|
||||
</div>;
|
||||
}
|
||||
|
||||
return <BaseDialog
|
||||
title={_t("Select spaces")}
|
||||
className="mx_ManageRestrictedJoinRuleDialog"
|
||||
onFinished={onFinished}
|
||||
fixedWidth={false}
|
||||
>
|
||||
<p>
|
||||
{ _t("Decide which spaces can access this room. " +
|
||||
"If a space is selected, its members can find and join <RoomName/>.", {}, {
|
||||
RoomName: () => <b>{ room.name }</b>,
|
||||
}) }
|
||||
</p>
|
||||
<MatrixClientContext.Provider value={cli}>
|
||||
<SearchBox
|
||||
className="mx_textinput_icon mx_textinput_search"
|
||||
placeholder={_t("Search spaces")}
|
||||
onSearch={setQuery}
|
||||
autoComplete={true}
|
||||
autoFocus={true}
|
||||
/>
|
||||
<AutoHideScrollbar className="mx_ManageRestrictedJoinRuleDialog_content">
|
||||
{ filteredSpacesContainingRooms.length > 0 ? (
|
||||
<div className="mx_ManageRestrictedJoinRuleDialog_section">
|
||||
<h3>{ _t("Spaces you know that contain this room") }</h3>
|
||||
{ filteredSpacesContainingRooms.map(space => {
|
||||
return <Entry
|
||||
key={space.roomId}
|
||||
room={space}
|
||||
checked={newSelected.has(space.roomId)}
|
||||
onChange={(checked: boolean) => {
|
||||
onChange(checked, space);
|
||||
}}
|
||||
/>;
|
||||
}) }
|
||||
</div>
|
||||
) : undefined }
|
||||
|
||||
{ filteredOtherEntries.length > 0 ? (
|
||||
<div className="mx_ManageRestrictedJoinRuleDialog_section">
|
||||
<h3>{ _t("Other spaces or rooms you might not know") }</h3>
|
||||
<div className="mx_ManageRestrictedJoinRuleDialog_section_info">
|
||||
<div>{ _t("These are likely ones other room admins are a part of.") }</div>
|
||||
</div>
|
||||
{ filteredOtherEntries.map(space => {
|
||||
return <Entry
|
||||
key={space.roomId}
|
||||
room={space}
|
||||
checked={newSelected.has(space.roomId)}
|
||||
onChange={(checked: boolean) => {
|
||||
onChange(checked, space);
|
||||
}}
|
||||
/>;
|
||||
}) }
|
||||
</div>
|
||||
) : null }
|
||||
|
||||
{ filteredSpacesContainingRooms.length + filteredOtherEntries.length < 1
|
||||
? <span className="mx_ManageRestrictedJoinRuleDialog_noResults">
|
||||
{ _t("No results") }
|
||||
</span>
|
||||
: undefined
|
||||
}
|
||||
</AutoHideScrollbar>
|
||||
|
||||
<div className="mx_ManageRestrictedJoinRuleDialog_footer">
|
||||
{ inviteOnlyWarning }
|
||||
<div className="mx_ManageRestrictedJoinRuleDialog_footer_buttons">
|
||||
<AccessibleButton kind="primary_outline" onClick={() => onFinished()}>
|
||||
{ _t("Cancel") }
|
||||
</AccessibleButton>
|
||||
<AccessibleButton kind="primary" onClick={() => onFinished(Array.from(newSelected))}>
|
||||
{ _t("Confirm") }
|
||||
</AccessibleButton>
|
||||
</div>
|
||||
</div>
|
||||
</MatrixClientContext.Provider>
|
||||
</BaseDialog>;
|
||||
};
|
||||
|
||||
export default ManageRestrictedJoinRuleDialog;
|
||||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -40,7 +40,7 @@ interface IState {
|
|||
busy: boolean;
|
||||
err?: string;
|
||||
// If we know it, the nature of the abuse, as specified by MSC3215.
|
||||
nature?: EXTENDED_NATURE;
|
||||
nature?: ExtendedNature;
|
||||
}
|
||||
|
||||
const MODERATED_BY_STATE_EVENT_TYPE = [
|
||||
|
@ -55,22 +55,22 @@ const MODERATED_BY_STATE_EVENT_TYPE = [
|
|||
const ABUSE_EVENT_TYPE = "org.matrix.msc3215.abuse.report";
|
||||
|
||||
// Standard abuse natures.
|
||||
enum NATURE {
|
||||
DISAGREEMENT = "org.matrix.msc3215.abuse.nature.disagreement",
|
||||
TOXIC = "org.matrix.msc3215.abuse.nature.toxic",
|
||||
ILLEGAL = "org.matrix.msc3215.abuse.nature.illegal",
|
||||
SPAM = "org.matrix.msc3215.abuse.nature.spam",
|
||||
OTHER = "org.matrix.msc3215.abuse.nature.other",
|
||||
enum Nature {
|
||||
Disagreement = "org.matrix.msc3215.abuse.nature.disagreement",
|
||||
Toxic = "org.matrix.msc3215.abuse.nature.toxic",
|
||||
Illegal = "org.matrix.msc3215.abuse.nature.illegal",
|
||||
Spam = "org.matrix.msc3215.abuse.nature.spam",
|
||||
Other = "org.matrix.msc3215.abuse.nature.other",
|
||||
}
|
||||
|
||||
enum NON_STANDARD_NATURE {
|
||||
enum NonStandardValue {
|
||||
// Non-standard abuse nature.
|
||||
// It should never leave the client - we use it to fallback to
|
||||
// server-wide abuse reporting.
|
||||
ADMIN = "non-standard.abuse.nature.admin"
|
||||
Admin = "non-standard.abuse.nature.admin"
|
||||
}
|
||||
|
||||
type EXTENDED_NATURE = NATURE | NON_STANDARD_NATURE;
|
||||
type ExtendedNature = Nature | NonStandardValue;
|
||||
|
||||
type Moderation = {
|
||||
// The id of the moderation room.
|
||||
|
@ -170,7 +170,7 @@ export default class ReportEventDialog extends React.Component<IProps, IState> {
|
|||
|
||||
// The user has clicked on a nature.
|
||||
private onNatureChosen = (e: React.FormEvent<HTMLInputElement>): void => {
|
||||
this.setState({ nature: e.currentTarget.value as EXTENDED_NATURE });
|
||||
this.setState({ nature: e.currentTarget.value as ExtendedNature });
|
||||
};
|
||||
|
||||
// The user has clicked "cancel".
|
||||
|
@ -187,7 +187,7 @@ export default class ReportEventDialog extends React.Component<IProps, IState> {
|
|||
// We need a nature.
|
||||
// If the nature is `NATURE.OTHER` or `NON_STANDARD_NATURE.ADMIN`, we also need a `reason`.
|
||||
if (!this.state.nature ||
|
||||
((this.state.nature == NATURE.OTHER || this.state.nature == NON_STANDARD_NATURE.ADMIN)
|
||||
((this.state.nature == Nature.Other || this.state.nature == NonStandardValue.Admin)
|
||||
&& !reason)
|
||||
) {
|
||||
this.setState({
|
||||
|
@ -214,8 +214,8 @@ export default class ReportEventDialog extends React.Component<IProps, IState> {
|
|||
try {
|
||||
const client = MatrixClientPeg.get();
|
||||
const ev = this.props.mxEvent;
|
||||
if (this.moderation && this.state.nature != NON_STANDARD_NATURE.ADMIN) {
|
||||
const nature: NATURE = this.state.nature;
|
||||
if (this.moderation && this.state.nature != NonStandardValue.Admin) {
|
||||
const nature: Nature = this.state.nature;
|
||||
|
||||
// Report to moderators through to the dedicated bot,
|
||||
// as configured in the room's state events.
|
||||
|
@ -245,7 +245,7 @@ export default class ReportEventDialog extends React.Component<IProps, IState> {
|
|||
let error = null;
|
||||
if (this.state.err) {
|
||||
error = <div className="error">
|
||||
{this.state.err}
|
||||
{ this.state.err }
|
||||
</div>;
|
||||
}
|
||||
|
||||
|
@ -274,27 +274,27 @@ export default class ReportEventDialog extends React.Component<IProps, IState> {
|
|||
const homeServerName = SdkConfig.get()["validated_server_config"].hsName;
|
||||
let subtitle;
|
||||
switch (this.state.nature) {
|
||||
case NATURE.DISAGREEMENT:
|
||||
case Nature.Disagreement:
|
||||
subtitle = _t("What this user is writing is wrong.\n" +
|
||||
"This will be reported to the room moderators.");
|
||||
break;
|
||||
case NATURE.TOXIC:
|
||||
case Nature.Toxic:
|
||||
subtitle = _t("This user is displaying toxic behaviour, " +
|
||||
"for instance by insulting other users or sharing " +
|
||||
" adult-only content in a family-friendly room " +
|
||||
" or otherwise violating the rules of this room.\n" +
|
||||
"This will be reported to the room moderators.");
|
||||
break;
|
||||
case NATURE.ILLEGAL:
|
||||
case Nature.Illegal:
|
||||
subtitle = _t("This user is displaying illegal behaviour, " +
|
||||
"for instance by doxing people or threatening violence.\n" +
|
||||
"This will be reported to the room moderators who may escalate this to legal authorities.");
|
||||
break;
|
||||
case NATURE.SPAM:
|
||||
case Nature.Spam:
|
||||
subtitle = _t("This user is spamming the room with ads, links to ads or to propaganda.\n" +
|
||||
"This will be reported to the room moderators.");
|
||||
break;
|
||||
case NON_STANDARD_NATURE.ADMIN:
|
||||
case NonStandardValue.Admin:
|
||||
if (client.isRoomEncrypted(this.props.mxEvent.getRoomId())) {
|
||||
subtitle = _t("This room is dedicated to illegal or toxic content " +
|
||||
"or the moderators fail to moderate illegal or toxic content.\n" +
|
||||
|
@ -308,7 +308,7 @@ export default class ReportEventDialog extends React.Component<IProps, IState> {
|
|||
{ homeserver: homeServerName });
|
||||
}
|
||||
break;
|
||||
case NATURE.OTHER:
|
||||
case Nature.Other:
|
||||
subtitle = _t("Any other reason. Please describe the problem.\n" +
|
||||
"This will be reported to the room moderators.");
|
||||
break;
|
||||
|
@ -326,55 +326,55 @@ export default class ReportEventDialog extends React.Component<IProps, IState> {
|
|||
>
|
||||
<div>
|
||||
<StyledRadioButton
|
||||
name = "nature"
|
||||
value = { NATURE.DISAGREEMENT }
|
||||
checked = { this.state.nature == NATURE.DISAGREEMENT }
|
||||
onChange = { this.onNatureChosen }
|
||||
name="nature"
|
||||
value={Nature.Disagreement}
|
||||
checked={this.state.nature == Nature.Disagreement}
|
||||
onChange={this.onNatureChosen}
|
||||
>
|
||||
{_t('Disagree')}
|
||||
{ _t('Disagree') }
|
||||
</StyledRadioButton>
|
||||
<StyledRadioButton
|
||||
name = "nature"
|
||||
value = { NATURE.TOXIC }
|
||||
checked = { this.state.nature == NATURE.TOXIC }
|
||||
onChange = { this.onNatureChosen }
|
||||
name="nature"
|
||||
value={Nature.Toxic}
|
||||
checked={this.state.nature == Nature.Toxic}
|
||||
onChange={this.onNatureChosen}
|
||||
>
|
||||
{_t('Toxic Behaviour')}
|
||||
{ _t('Toxic Behaviour') }
|
||||
</StyledRadioButton>
|
||||
<StyledRadioButton
|
||||
name = "nature"
|
||||
value = { NATURE.ILLEGAL }
|
||||
checked = { this.state.nature == NATURE.ILLEGAL }
|
||||
onChange = { this.onNatureChosen }
|
||||
name="nature"
|
||||
value={Nature.Illegal}
|
||||
checked={this.state.nature == Nature.Illegal}
|
||||
onChange={this.onNatureChosen}
|
||||
>
|
||||
{_t('Illegal Content')}
|
||||
{ _t('Illegal Content') }
|
||||
</StyledRadioButton>
|
||||
<StyledRadioButton
|
||||
name = "nature"
|
||||
value = { NATURE.SPAM }
|
||||
checked = { this.state.nature == NATURE.SPAM }
|
||||
onChange = { this.onNatureChosen }
|
||||
name="nature"
|
||||
value={Nature.Spam}
|
||||
checked={this.state.nature == Nature.Spam}
|
||||
onChange={this.onNatureChosen}
|
||||
>
|
||||
{_t('Spam or propaganda')}
|
||||
{ _t('Spam or propaganda') }
|
||||
</StyledRadioButton>
|
||||
<StyledRadioButton
|
||||
name = "nature"
|
||||
value = { NON_STANDARD_NATURE.ADMIN }
|
||||
checked = { this.state.nature == NON_STANDARD_NATURE.ADMIN }
|
||||
onChange = { this.onNatureChosen }
|
||||
name="nature"
|
||||
value={NonStandardValue.Admin}
|
||||
checked={this.state.nature == NonStandardValue.Admin}
|
||||
onChange={this.onNatureChosen}
|
||||
>
|
||||
{_t('Report the entire room')}
|
||||
{ _t('Report the entire room') }
|
||||
</StyledRadioButton>
|
||||
<StyledRadioButton
|
||||
name = "nature"
|
||||
value = { NATURE.OTHER }
|
||||
checked = { this.state.nature == NATURE.OTHER }
|
||||
onChange = { this.onNatureChosen }
|
||||
name="nature"
|
||||
value={Nature.Other}
|
||||
checked={this.state.nature == Nature.Other}
|
||||
onChange={this.onNatureChosen}
|
||||
>
|
||||
{_t('Other')}
|
||||
{ _t('Other') }
|
||||
</StyledRadioButton>
|
||||
<p>
|
||||
{subtitle}
|
||||
{ subtitle }
|
||||
</p>
|
||||
<Field
|
||||
className="mx_ReportEventDialog_reason"
|
||||
|
@ -385,8 +385,8 @@ export default class ReportEventDialog extends React.Component<IProps, IState> {
|
|||
value={this.state.reason}
|
||||
disabled={this.state.busy}
|
||||
/>
|
||||
{progress}
|
||||
{error}
|
||||
{ progress }
|
||||
{ error }
|
||||
</div>
|
||||
<DialogButtons
|
||||
primaryButton={_t("Send report")}
|
||||
|
@ -416,7 +416,7 @@ export default class ReportEventDialog extends React.Component<IProps, IState> {
|
|||
"or images.")
|
||||
}
|
||||
</p>
|
||||
{adminMessage}
|
||||
{ adminMessage }
|
||||
<Field
|
||||
className="mx_ReportEventDialog_reason"
|
||||
element="textarea"
|
||||
|
@ -426,8 +426,8 @@ export default class ReportEventDialog extends React.Component<IProps, IState> {
|
|||
value={this.state.reason}
|
||||
disabled={this.state.busy}
|
||||
/>
|
||||
{progress}
|
||||
{error}
|
||||
{ progress }
|
||||
{ error }
|
||||
</div>
|
||||
<DialogButtons
|
||||
primaryButton={_t("Send report")}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright 2018 New Vector Ltd
|
||||
Copyright 2018 - 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,19 +15,29 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import * as sdk from '../../../index';
|
||||
import { MatrixClientPeg } from '../../../MatrixClientPeg';
|
||||
import { Room } from "matrix-js-sdk/src/models/room";
|
||||
|
||||
import Modal from '../../../Modal';
|
||||
import { _t } from '../../../languageHandler';
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import { upgradeRoom } from "../../../utils/RoomUpgrade";
|
||||
import { IDialogProps } from "./IDialogProps";
|
||||
import BaseDialog from "./BaseDialog";
|
||||
import ErrorDialog from './ErrorDialog';
|
||||
import DialogButtons from '../elements/DialogButtons';
|
||||
import Spinner from "../elements/Spinner";
|
||||
|
||||
interface IProps extends IDialogProps {
|
||||
room: Room;
|
||||
}
|
||||
|
||||
interface IState {
|
||||
busy: boolean;
|
||||
}
|
||||
|
||||
@replaceableComponent("views.dialogs.RoomUpgradeDialog")
|
||||
export default class RoomUpgradeDialog extends React.Component {
|
||||
static propTypes = {
|
||||
room: PropTypes.object.isRequired,
|
||||
onFinished: PropTypes.func.isRequired,
|
||||
};
|
||||
export default class RoomUpgradeDialog extends React.Component<IProps, IState> {
|
||||
private targetVersion: string;
|
||||
|
||||
state = {
|
||||
busy: true,
|
||||
|
@ -35,20 +45,19 @@ export default class RoomUpgradeDialog extends React.Component {
|
|||
|
||||
async componentDidMount() {
|
||||
const recommended = await this.props.room.getRecommendedVersion();
|
||||
this._targetVersion = recommended.version;
|
||||
this.targetVersion = recommended.version;
|
||||
this.setState({ busy: false });
|
||||
}
|
||||
|
||||
_onCancelClick = () => {
|
||||
private onCancelClick = (): void => {
|
||||
this.props.onFinished(false);
|
||||
};
|
||||
|
||||
_onUpgradeClick = () => {
|
||||
private onUpgradeClick = (): void => {
|
||||
this.setState({ busy: true });
|
||||
MatrixClientPeg.get().upgradeRoom(this.props.room.roomId, this._targetVersion).then(() => {
|
||||
upgradeRoom(this.props.room, this.targetVersion, false, false).then(() => {
|
||||
this.props.onFinished(true);
|
||||
}).catch((err) => {
|
||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
Modal.createTrackedDialog('Failed to upgrade room', '', ErrorDialog, {
|
||||
title: _t("Failed to upgrade room"),
|
||||
description: ((err && err.message) ? err.message : _t("The room upgrade could not be completed")),
|
||||
|
@ -59,48 +68,43 @@ export default class RoomUpgradeDialog extends React.Component {
|
|||
};
|
||||
|
||||
render() {
|
||||
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
|
||||
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
|
||||
const Spinner = sdk.getComponent('views.elements.Spinner');
|
||||
|
||||
let buttons;
|
||||
if (this.state.busy) {
|
||||
buttons = <Spinner />;
|
||||
} else {
|
||||
buttons = <DialogButtons
|
||||
primaryButton={_t(
|
||||
'Upgrade this room to version %(version)s',
|
||||
{ version: this._targetVersion },
|
||||
)}
|
||||
primaryButton={_t('Upgrade this room to version %(version)s', { version: this.targetVersion })}
|
||||
primaryButtonClass="danger"
|
||||
hasCancel={true}
|
||||
onPrimaryButtonClick={this._onUpgradeClick}
|
||||
focus={this.props.focus}
|
||||
onCancel={this._onCancelClick}
|
||||
onPrimaryButtonClick={this.onUpgradeClick}
|
||||
onCancel={this.onCancelClick}
|
||||
/>;
|
||||
}
|
||||
|
||||
return (
|
||||
<BaseDialog className="mx_RoomUpgradeDialog"
|
||||
<BaseDialog
|
||||
className="mx_RoomUpgradeDialog"
|
||||
onFinished={this.props.onFinished}
|
||||
title={_t("Upgrade Room Version")}
|
||||
contentId='mx_Dialog_content'
|
||||
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>
|
||||
);
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright 2019, 2020 The Matrix.org Foundation C.I.C.
|
||||
Copyright 2019 - 2021 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
@ -14,86 +14,95 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { ReactNode } from 'react';
|
||||
import { EventType } from 'matrix-js-sdk/src/@types/event';
|
||||
import { JoinRule } from 'matrix-js-sdk/src/@types/partials';
|
||||
|
||||
import { _t } from "../../../languageHandler";
|
||||
import SdkConfig from "../../../SdkConfig";
|
||||
import * as sdk from "../../../index";
|
||||
import LabelledToggleSwitch from "../elements/LabelledToggleSwitch";
|
||||
import { MatrixClientPeg } from "../../../MatrixClientPeg";
|
||||
import Modal from "../../../Modal";
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import { IDialogProps } from "./IDialogProps";
|
||||
import BugReportDialog from './BugReportDialog';
|
||||
import BaseDialog from "./BaseDialog";
|
||||
import DialogButtons from "../elements/DialogButtons";
|
||||
|
||||
interface IProps extends IDialogProps {
|
||||
roomId: string;
|
||||
targetVersion: string;
|
||||
description?: ReactNode;
|
||||
}
|
||||
|
||||
interface IState {
|
||||
inviteUsersToNewRoom: boolean;
|
||||
}
|
||||
|
||||
@replaceableComponent("views.dialogs.RoomUpgradeWarningDialog")
|
||||
export default class RoomUpgradeWarningDialog extends React.Component {
|
||||
static propTypes = {
|
||||
onFinished: PropTypes.func.isRequired,
|
||||
roomId: PropTypes.string.isRequired,
|
||||
targetVersion: PropTypes.string.isRequired,
|
||||
};
|
||||
export default class RoomUpgradeWarningDialog extends React.Component<IProps, IState> {
|
||||
private readonly isPrivate: boolean;
|
||||
private readonly currentVersion: string;
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
const room = MatrixClientPeg.get().getRoom(this.props.roomId);
|
||||
const joinRules = room ? room.currentState.getStateEvents("m.room.join_rules", "") : null;
|
||||
const isPrivate = joinRules ? joinRules.getContent()['join_rule'] !== 'public' : true;
|
||||
const joinRules = room?.currentState.getStateEvents(EventType.RoomJoinRules, "");
|
||||
this.isPrivate = joinRules?.getContent()['join_rule'] !== JoinRule.Public ?? true;
|
||||
this.currentVersion = room?.getVersion() || "1";
|
||||
|
||||
this.state = {
|
||||
currentVersion: room ? room.getVersion() : "1",
|
||||
isPrivate,
|
||||
inviteUsersToNewRoom: true,
|
||||
};
|
||||
}
|
||||
|
||||
_onContinue = () => {
|
||||
this.props.onFinished({ continue: true, invite: this.state.isPrivate && this.state.inviteUsersToNewRoom });
|
||||
private onContinue = () => {
|
||||
this.props.onFinished({ continue: true, invite: this.isPrivate && this.state.inviteUsersToNewRoom });
|
||||
};
|
||||
|
||||
_onCancel = () => {
|
||||
private onCancel = () => {
|
||||
this.props.onFinished({ continue: false, invite: false });
|
||||
};
|
||||
|
||||
_onInviteUsersToggle = (newVal) => {
|
||||
this.setState({ inviteUsersToNewRoom: newVal });
|
||||
private onInviteUsersToggle = (inviteUsersToNewRoom: boolean) => {
|
||||
this.setState({ inviteUsersToNewRoom });
|
||||
};
|
||||
|
||||
_openBugReportDialog = (e) => {
|
||||
private openBugReportDialog = (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
const BugReportDialog = sdk.getComponent("dialogs.BugReportDialog");
|
||||
Modal.createTrackedDialog('Bug Report Dialog', '', BugReportDialog, {});
|
||||
};
|
||||
|
||||
render() {
|
||||
const brand = SdkConfig.get().brand;
|
||||
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
|
||||
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
|
||||
|
||||
let inviteToggle = null;
|
||||
if (this.state.isPrivate) {
|
||||
if (this.isPrivate) {
|
||||
inviteToggle = (
|
||||
<LabelledToggleSwitch
|
||||
value={this.state.inviteUsersToNewRoom}
|
||||
onChange={this._onInviteUsersToggle}
|
||||
label={_t("Automatically invite users")} />
|
||||
onChange={this.onInviteUsersToggle}
|
||||
label={_t("Automatically invite members from this room to the new one")} />
|
||||
);
|
||||
}
|
||||
|
||||
const title = this.state.isPrivate ? _t("Upgrade private room") : _t("Upgrade public room");
|
||||
const title = this.isPrivate ? _t("Upgrade private room") : _t("Upgrade public room");
|
||||
|
||||
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 +110,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,29 +128,37 @@ export default class RoomUpgradeWarningDialog extends React.Component {
|
|||
>
|
||||
<div>
|
||||
<p>
|
||||
{_t(
|
||||
{ this.props.description || _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}
|
||||
<p>
|
||||
{_t(
|
||||
{ _t(
|
||||
"<b>Please note upgrading will make a new version of the room</b>. " +
|
||||
"All current messages will stay in this archived room.", {}, {
|
||||
b: sub => <b>{ sub }</b>,
|
||||
},
|
||||
) }
|
||||
</p>
|
||||
{ bugReports }
|
||||
<p>
|
||||
{ _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.currentVersion }</code>,
|
||||
newVersion: () => <code>{ this.props.targetVersion }</code>,
|
||||
},
|
||||
)}
|
||||
) }
|
||||
</p>
|
||||
{inviteToggle}
|
||||
{ inviteToggle }
|
||||
</div>
|
||||
<DialogButtons
|
||||
primaryButton={_t("Upgrade")}
|
||||
onPrimaryButtonClick={this._onContinue}
|
||||
onPrimaryButtonClick={this.onContinue}
|
||||
cancelButton={_t("Cancel")}
|
||||
onCancel={this._onCancel}
|
||||
onCancel={this.onCancel}
|
||||
/>
|
||||
</BaseDialog>
|
||||
);
|
|
@ -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>;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
@ -222,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>;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -85,7 +85,9 @@ export default class SessionRestoreErrorDialog extends React.Component {
|
|||
}
|
||||
|
||||
return (
|
||||
<BaseDialog className="mx_ErrorDialog" onFinished={this.props.onFinished}
|
||||
<BaseDialog
|
||||
className="mx_ErrorDialog"
|
||||
onFinished={this.props.onFinished}
|
||||
title={_t('Unable to restore session')}
|
||||
contentId='mx_Dialog_content'
|
||||
hasCancel={false}
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -48,27 +48,29 @@ 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>,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<BaseDialog className="mx_ErrorDialog" onFinished={this.props.onFinished}
|
||||
<BaseDialog
|
||||
className="mx_ErrorDialog"
|
||||
onFinished={this.props.onFinished}
|
||||
title={_t('Missing session data')}
|
||||
contentId='mx_Dialog_content'
|
||||
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}
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
|
|
|
@ -90,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>;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -100,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>;
|
||||
}
|
||||
}
|
||||
|
@ -136,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>
|
||||
|
@ -186,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>
|
||||
|
||||
|
|
|
@ -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")}>
|
||||
|
|
|
@ -86,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 {
|
||||
|
@ -95,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>;
|
||||
}
|
||||
|
@ -103,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>;
|
||||
}
|
||||
|
||||
|
@ -115,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')}
|
||||
|
@ -123,7 +123,7 @@ export default class UploadConfirmDialog extends React.Component<IProps> {
|
|||
onPrimaryButtonClick={this.onUploadClick}
|
||||
focus={true}
|
||||
>
|
||||
{uploadAllButton}
|
||||
{ uploadAllButton }
|
||||
</DialogButtons>
|
||||
</BaseDialog>
|
||||
);
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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(
|
||||
|
@ -170,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>
|
||||
);
|
||||
|
|
|
@ -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")}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -285,11 +285,12 @@ 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>,
|
||||
})}
|
||||
href=""
|
||||
onClick={this.onResetAllClick}
|
||||
className="mx_AccessSecretStorageDialog_reset_link">{ sub }</a>,
|
||||
}) }
|
||||
</div>
|
||||
);
|
||||
|
||||
|
@ -300,9 +301,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 +321,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 +354,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 +376,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 +400,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 +409,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 +436,7 @@ export default class AccessSecretStorageDialog extends React.PureComponent<IProp
|
|||
titleClass={titleClass}
|
||||
>
|
||||
<div>
|
||||
{content}
|
||||
{ content }
|
||||
</div>
|
||||
</BaseDialog>
|
||||
);
|
||||
|
|
|
@ -44,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
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
|
|
|
@ -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");
|
||||
|
@ -399,27 +399,27 @@ export default class RestoreKeyBackupDialog extends React.PureComponent {
|
|||
|
||||
let keyStatus;
|
||||
if (this.state.recoveryKey.length === 0) {
|
||||
keyStatus = <div className="mx_RestoreKeyBackupDialog_keyStatus"></div>;
|
||||
keyStatus = <div className="mx_RestoreKeyBackupDialog_keyStatus" />;
|
||||
} 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>
|
||||
);
|
||||
|
|
|
@ -184,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>
|
||||
);
|
||||
}
|
||||
|
@ -238,7 +238,7 @@ const NetworkDropdown = ({ onOptionChange, protocols = {}, selectedServerName, s
|
|||
label={_t("Matrix")}
|
||||
className="mx_NetworkDropdown_server_network"
|
||||
>
|
||||
{_t("Matrix")}
|
||||
{ _t("Matrix") }
|
||||
</MenuItemRadio>
|
||||
{ entries }
|
||||
</MenuGroup>
|
||||
|
@ -270,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>;
|
||||
|
@ -295,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>;
|
||||
};
|
||||
|
||||
|
|
|
@ -15,56 +15,62 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import AccessibleButton from './AccessibleButton';
|
||||
import dis from '../../../dispatcher/dispatcher';
|
||||
import * as sdk from '../../../index';
|
||||
import Analytics from '../../../Analytics';
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import Tooltip from './Tooltip';
|
||||
|
||||
interface IProps {
|
||||
size?: string;
|
||||
tooltip?: boolean;
|
||||
action: string;
|
||||
mouseOverAction?: string;
|
||||
label: string;
|
||||
iconPath?: string;
|
||||
className?: string;
|
||||
children?: JSX.Element;
|
||||
}
|
||||
|
||||
interface IState {
|
||||
showTooltip: boolean;
|
||||
}
|
||||
|
||||
@replaceableComponent("views.elements.ActionButton")
|
||||
export default class ActionButton extends React.Component {
|
||||
static propTypes = {
|
||||
size: PropTypes.string,
|
||||
tooltip: PropTypes.bool,
|
||||
action: PropTypes.string.isRequired,
|
||||
mouseOverAction: PropTypes.string,
|
||||
label: PropTypes.string.isRequired,
|
||||
iconPath: PropTypes.string,
|
||||
className: PropTypes.string,
|
||||
children: PropTypes.node,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
export default class ActionButton extends React.Component<IProps, IState> {
|
||||
static defaultProps: Partial<IProps> = {
|
||||
size: "25",
|
||||
tooltip: false,
|
||||
};
|
||||
|
||||
state = {
|
||||
showTooltip: false,
|
||||
};
|
||||
constructor(props: IProps) {
|
||||
super(props);
|
||||
|
||||
_onClick = (ev) => {
|
||||
this.state = {
|
||||
showTooltip: false,
|
||||
};
|
||||
}
|
||||
|
||||
private onClick = (ev: React.MouseEvent): void => {
|
||||
ev.stopPropagation();
|
||||
Analytics.trackEvent('Action Button', 'click', this.props.action);
|
||||
dis.dispatch({ action: this.props.action });
|
||||
};
|
||||
|
||||
_onMouseEnter = () => {
|
||||
private onMouseEnter = (): void => {
|
||||
if (this.props.tooltip) this.setState({ showTooltip: true });
|
||||
if (this.props.mouseOverAction) {
|
||||
dis.dispatch({ action: this.props.mouseOverAction });
|
||||
}
|
||||
};
|
||||
|
||||
_onMouseLeave = () => {
|
||||
private onMouseLeave = (): void => {
|
||||
this.setState({ showTooltip: false });
|
||||
};
|
||||
|
||||
render() {
|
||||
let tooltip;
|
||||
if (this.state.showTooltip) {
|
||||
const Tooltip = sdk.getComponent("elements.Tooltip");
|
||||
tooltip = <Tooltip className="mx_RoleButton_tooltip" label={this.props.label} />;
|
||||
}
|
||||
|
||||
|
@ -80,9 +86,9 @@ export default class ActionButton extends React.Component {
|
|||
return (
|
||||
<AccessibleButton
|
||||
className={classNames.join(" ")}
|
||||
onClick={this._onClick}
|
||||
onMouseEnter={this._onMouseEnter}
|
||||
onMouseLeave={this._onMouseLeave}
|
||||
onClick={this.onClick}
|
||||
onMouseEnter={this.onMouseEnter}
|
||||
onMouseLeave={this.onMouseLeave}
|
||||
aria-label={this.props.label}
|
||||
>
|
||||
{ icon }
|
|
@ -15,30 +15,37 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import * as sdk from '../../../index';
|
||||
import React, { createRef } from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { UserAddressType } from '../../../UserAddress';
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import { IUserAddress } from '../../../UserAddress';
|
||||
import AddressTile from './AddressTile';
|
||||
|
||||
interface IProps {
|
||||
onSelected: (index: number) => void;
|
||||
|
||||
// List of the addresses to display
|
||||
addressList: IUserAddress[];
|
||||
// Whether to show the address on the address tiles
|
||||
showAddress?: boolean;
|
||||
truncateAt: number;
|
||||
selected?: number;
|
||||
|
||||
// Element to put as a header on top of the list
|
||||
header?: JSX.Element;
|
||||
}
|
||||
|
||||
interface IState {
|
||||
selected: number;
|
||||
hover: boolean;
|
||||
}
|
||||
|
||||
@replaceableComponent("views.elements.AddressSelector")
|
||||
export default class AddressSelector extends React.Component {
|
||||
static propTypes = {
|
||||
onSelected: PropTypes.func.isRequired,
|
||||
export default class AddressSelector extends React.Component<IProps, IState> {
|
||||
private scrollElement = createRef<HTMLDivElement>();
|
||||
private addressListElement = createRef<HTMLDivElement>();
|
||||
|
||||
// List of the addresses to display
|
||||
addressList: PropTypes.arrayOf(UserAddressType).isRequired,
|
||||
// Whether to show the address on the address tiles
|
||||
showAddress: PropTypes.bool,
|
||||
truncateAt: PropTypes.number.isRequired,
|
||||
selected: PropTypes.number,
|
||||
|
||||
// Element to put as a header on top of the list
|
||||
header: PropTypes.node,
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
constructor(props: IProps) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
|
@ -48,10 +55,10 @@ export default class AddressSelector extends React.Component {
|
|||
}
|
||||
|
||||
// TODO: [REACT-WARNING] Replace with appropriate lifecycle event
|
||||
UNSAFE_componentWillReceiveProps(props) { // eslint-disable-line camelcase
|
||||
UNSAFE_componentWillReceiveProps(props: IProps) { // eslint-disable-line
|
||||
// Make sure the selected item isn't outside the list bounds
|
||||
const selected = this.state.selected;
|
||||
const maxSelected = this._maxSelected(props.addressList);
|
||||
const maxSelected = this.maxSelected(props.addressList);
|
||||
if (selected > maxSelected) {
|
||||
this.setState({ selected: maxSelected });
|
||||
}
|
||||
|
@ -60,13 +67,13 @@ export default class AddressSelector extends React.Component {
|
|||
componentDidUpdate() {
|
||||
// As the user scrolls with the arrow keys keep the selected item
|
||||
// at the top of the window.
|
||||
if (this.scrollElement && this.props.addressList.length > 0 && !this.state.hover) {
|
||||
const elementHeight = this.addressListElement.getBoundingClientRect().height;
|
||||
this.scrollElement.scrollTop = (this.state.selected * elementHeight) - elementHeight;
|
||||
if (this.scrollElement.current && this.props.addressList.length > 0 && !this.state.hover) {
|
||||
const elementHeight = this.addressListElement.current.getBoundingClientRect().height;
|
||||
this.scrollElement.current.scrollTop = (this.state.selected * elementHeight) - elementHeight;
|
||||
}
|
||||
}
|
||||
|
||||
moveSelectionTop = () => {
|
||||
public moveSelectionTop = (): void => {
|
||||
if (this.state.selected > 0) {
|
||||
this.setState({
|
||||
selected: 0,
|
||||
|
@ -75,7 +82,7 @@ export default class AddressSelector extends React.Component {
|
|||
}
|
||||
};
|
||||
|
||||
moveSelectionUp = () => {
|
||||
public moveSelectionUp = (): void => {
|
||||
if (this.state.selected > 0) {
|
||||
this.setState({
|
||||
selected: this.state.selected - 1,
|
||||
|
@ -84,8 +91,8 @@ export default class AddressSelector extends React.Component {
|
|||
}
|
||||
};
|
||||
|
||||
moveSelectionDown = () => {
|
||||
if (this.state.selected < this._maxSelected(this.props.addressList)) {
|
||||
public moveSelectionDown = (): void => {
|
||||
if (this.state.selected < this.maxSelected(this.props.addressList)) {
|
||||
this.setState({
|
||||
selected: this.state.selected + 1,
|
||||
hover: false,
|
||||
|
@ -93,26 +100,26 @@ export default class AddressSelector extends React.Component {
|
|||
}
|
||||
};
|
||||
|
||||
chooseSelection = () => {
|
||||
public chooseSelection = (): void => {
|
||||
this.selectAddress(this.state.selected);
|
||||
};
|
||||
|
||||
onClick = index => {
|
||||
private onClick = (index: number): void => {
|
||||
this.selectAddress(index);
|
||||
};
|
||||
|
||||
onMouseEnter = index => {
|
||||
private onMouseEnter = (index: number): void => {
|
||||
this.setState({
|
||||
selected: index,
|
||||
hover: true,
|
||||
});
|
||||
};
|
||||
|
||||
onMouseLeave = () => {
|
||||
private onMouseLeave = (): void => {
|
||||
this.setState({ hover: false });
|
||||
};
|
||||
|
||||
selectAddress = index => {
|
||||
private selectAddress = (index: number): void => {
|
||||
// Only try to select an address if one exists
|
||||
if (this.props.addressList.length !== 0) {
|
||||
this.props.onSelected(index);
|
||||
|
@ -120,9 +127,8 @@ export default class AddressSelector extends React.Component {
|
|||
}
|
||||
};
|
||||
|
||||
createAddressListTiles() {
|
||||
const AddressTile = sdk.getComponent("elements.AddressTile");
|
||||
const maxSelected = this._maxSelected(this.props.addressList);
|
||||
private createAddressListTiles(): JSX.Element[] {
|
||||
const maxSelected = this.maxSelected(this.props.addressList);
|
||||
const addressList = [];
|
||||
|
||||
// Only create the address elements if there are address
|
||||
|
@ -143,14 +149,12 @@ export default class AddressSelector extends React.Component {
|
|||
onMouseEnter={this.onMouseEnter.bind(this, i)}
|
||||
onMouseLeave={this.onMouseLeave}
|
||||
key={this.props.addressList[i].addressType + "/" + this.props.addressList[i].address}
|
||||
ref={(ref) => { this.addressListElement = ref; }}
|
||||
ref={this.addressListElement}
|
||||
>
|
||||
<AddressTile
|
||||
address={this.props.addressList[i]}
|
||||
showAddress={this.props.showAddress}
|
||||
justified={true}
|
||||
networkName="vector"
|
||||
networkUrl={require("../../../../res/img/search-icon-vector.svg")}
|
||||
/>
|
||||
</div>,
|
||||
);
|
||||
|
@ -159,7 +163,7 @@ export default class AddressSelector extends React.Component {
|
|||
return addressList;
|
||||
}
|
||||
|
||||
_maxSelected(list) {
|
||||
private maxSelected(list: IUserAddress[]): number {
|
||||
const listSize = list.length === 0 ? 0 : list.length - 1;
|
||||
const maxSelected = listSize > (this.props.truncateAt - 1) ? (this.props.truncateAt - 1) : listSize;
|
||||
return maxSelected;
|
||||
|
@ -172,7 +176,7 @@ export default class AddressSelector extends React.Component {
|
|||
});
|
||||
|
||||
return (
|
||||
<div className={classes} ref={(ref) => {this.scrollElement = ref;}}>
|
||||
<div className={classes} ref={this.scrollElement}>
|
||||
{ this.props.header }
|
||||
{ this.createAddressListTiles() }
|
||||
</div>
|
|
@ -16,24 +16,25 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classNames from 'classnames';
|
||||
import * as sdk from "../../../index";
|
||||
import { _t } from '../../../languageHandler';
|
||||
import { UserAddressType } from '../../../UserAddress';
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import { mediaFromMxc } from "../../../customisations/Media";
|
||||
import { IUserAddress } from '../../../UserAddress';
|
||||
import BaseAvatar from '../avatars/BaseAvatar';
|
||||
import EmailUserIcon from "../../../../res/img/icon-email-user.svg";
|
||||
|
||||
interface IProps {
|
||||
address: IUserAddress;
|
||||
canDismiss?: boolean;
|
||||
onDismissed?: () => void;
|
||||
justified?: boolean;
|
||||
showAddress?: boolean;
|
||||
}
|
||||
|
||||
@replaceableComponent("views.elements.AddressTile")
|
||||
export default class AddressTile extends React.Component {
|
||||
static propTypes = {
|
||||
address: UserAddressType.isRequired,
|
||||
canDismiss: PropTypes.bool,
|
||||
onDismissed: PropTypes.func,
|
||||
justified: PropTypes.bool,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
export default class AddressTile extends React.Component<IProps> {
|
||||
static defaultProps: Partial<IProps> = {
|
||||
canDismiss: false,
|
||||
onDismissed: function() {}, // NOP
|
||||
justified: false,
|
||||
|
@ -49,11 +50,9 @@ export default class AddressTile extends React.Component {
|
|||
if (isMatrixAddress && address.avatarMxc) {
|
||||
imgUrls.push(mediaFromMxc(address.avatarMxc).getSquareThumbnailHttp(25));
|
||||
} else if (address.addressType === 'email') {
|
||||
imgUrls.push(require("../../../../res/img/icon-email-user.svg"));
|
||||
imgUrls.push(EmailUserIcon);
|
||||
}
|
||||
|
||||
const BaseAvatar = sdk.getComponent('avatars.BaseAvatar');
|
||||
|
||||
const nameClasses = classNames({
|
||||
"mx_AddressTile_name": true,
|
||||
"mx_AddressTile_justified": this.props.justified,
|
||||
|
@ -70,9 +69,10 @@ export default class AddressTile extends React.Component {
|
|||
info = (
|
||||
<div className="mx_AddressTile_mx">
|
||||
<div className={nameClasses}>{ name }</div>
|
||||
{ this.props.showAddress ?
|
||||
<div className={idClasses}>{ address.address }</div> :
|
||||
<div />
|
||||
{
|
||||
this.props.showAddress
|
||||
? <div className={idClasses}>{ address.address }</div>
|
||||
: <div />
|
||||
}
|
||||
</div>
|
||||
);
|
||||
|
@ -122,7 +122,7 @@ export default class AddressTile extends React.Component {
|
|||
let dismiss;
|
||||
if (this.props.canDismiss) {
|
||||
dismiss = (
|
||||
<div className="mx_AddressTile_dismiss" onClick={this.props.onDismissed} >
|
||||
<div className="mx_AddressTile_dismiss" onClick={this.props.onDismissed}>
|
||||
<img src={require("../../../../res/img/icon-address-delete.svg")} width="9" height="9" />
|
||||
</div>
|
||||
);
|
|
@ -17,30 +17,39 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import url from 'url';
|
||||
import * as sdk from '../../../index';
|
||||
import { _t } from '../../../languageHandler';
|
||||
import SdkConfig from '../../../SdkConfig';
|
||||
import WidgetUtils from "../../../utils/WidgetUtils";
|
||||
import { MatrixClientPeg } from "../../../MatrixClientPeg";
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import { RoomMember } from 'matrix-js-sdk/src/models/room-member';
|
||||
import MemberAvatar from '../avatars/MemberAvatar';
|
||||
import BaseAvatar from '../avatars/BaseAvatar';
|
||||
import AccessibleButton from './AccessibleButton';
|
||||
import TextWithTooltip from "./TextWithTooltip";
|
||||
|
||||
interface IProps {
|
||||
url: string;
|
||||
creatorUserId: string;
|
||||
roomId: string;
|
||||
onPermissionGranted: () => void;
|
||||
isRoomEncrypted?: boolean;
|
||||
}
|
||||
|
||||
interface IState {
|
||||
roomMember: RoomMember;
|
||||
isWrapped: boolean;
|
||||
widgetDomain: string;
|
||||
}
|
||||
|
||||
@replaceableComponent("views.elements.AppPermission")
|
||||
export default class AppPermission extends React.Component {
|
||||
static propTypes = {
|
||||
url: PropTypes.string.isRequired,
|
||||
creatorUserId: PropTypes.string.isRequired,
|
||||
roomId: PropTypes.string.isRequired,
|
||||
onPermissionGranted: PropTypes.func.isRequired,
|
||||
isRoomEncrypted: PropTypes.bool,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
export default class AppPermission extends React.Component<IProps, IState> {
|
||||
static defaultProps: Partial<IProps> = {
|
||||
onPermissionGranted: () => {},
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
constructor(props: IProps) {
|
||||
super(props);
|
||||
|
||||
// The first step is to pick apart the widget so we can render information about it
|
||||
|
@ -55,16 +64,18 @@ export default class AppPermission extends React.Component {
|
|||
this.state = {
|
||||
...urlInfo,
|
||||
roomMember,
|
||||
isWrapped: null,
|
||||
widgetDomain: null,
|
||||
};
|
||||
}
|
||||
|
||||
parseWidgetUrl() {
|
||||
private parseWidgetUrl(): { isWrapped: boolean, widgetDomain: string } {
|
||||
const widgetUrl = url.parse(this.props.url);
|
||||
const params = new URLSearchParams(widgetUrl.search);
|
||||
|
||||
// HACK: We're relying on the query params when we should be relying on the widget's `data`.
|
||||
// This is a workaround for Scalar.
|
||||
if (WidgetUtils.isScalarUrl(widgetUrl) && params && params.get('url')) {
|
||||
if (WidgetUtils.isScalarUrl(this.props.url) && params && params.get('url')) {
|
||||
const unwrappedUrl = url.parse(params.get('url'));
|
||||
return {
|
||||
widgetDomain: unwrappedUrl.host || unwrappedUrl.hostname,
|
||||
|
@ -80,10 +91,6 @@ export default class AppPermission extends React.Component {
|
|||
|
||||
render() {
|
||||
const brand = SdkConfig.get().brand;
|
||||
const AccessibleButton = sdk.getComponent("views.elements.AccessibleButton");
|
||||
const MemberAvatar = sdk.getComponent("views.avatars.MemberAvatar");
|
||||
const BaseAvatar = sdk.getComponent("views.avatars.BaseAvatar");
|
||||
const TextWithTooltip = sdk.getComponent("views.elements.TextWithTooltip");
|
||||
|
||||
const displayName = this.state.roomMember ? this.state.roomMember.name : this.props.creatorUserId;
|
||||
const userId = displayName === this.props.creatorUserId ? null : this.props.creatorUserId;
|
||||
|
@ -94,15 +101,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>
|
||||
);
|
||||
|
@ -124,22 +131,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.")} {encryptionWarning}
|
||||
{ _t("This widget may use cookies.") } { encryptionWarning }
|
||||
</div>
|
||||
<div className='mx_AppPermissionWarning_row'>
|
||||
<AccessibleButton kind='primary_sm' onClick={this.props.onPermissionGranted}>
|
||||
{_t("Continue")}
|
||||
{ _t("Continue") }
|
||||
</AccessibleButton>
|
||||
</div>
|
||||
</div>
|
|
@ -408,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>;
|
||||
}
|
||||
|
@ -453,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 }
|
||||
|
|
|
@ -41,6 +41,6 @@ export default function DNDTagTile(props) {
|
|||
menuDisplayed={menuDisplayed}
|
||||
openMenu={openMenu}
|
||||
/>
|
||||
{contextMenu}
|
||||
{ contextMenu }
|
||||
</>;
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue