Merge branch 'develop' of https://github.com/matrix-org/matrix-react-sdk into t3chguy/dpsah/6785

 Conflicts:
	src/components/structures/ScrollPanel.js
	src/components/views/rooms/AppsDrawer.js
This commit is contained in:
Michael Telatynski 2020-09-07 14:33:57 +01:00
commit 667c129ebc
162 changed files with 4010 additions and 3800 deletions

View file

@ -18,16 +18,13 @@ limitations under the License.
import { _t } from '../../../languageHandler';
import React from 'react';
import createReactClass from 'create-react-class';
export default createReactClass({
displayName: 'AuthFooter',
render: function() {
export default class AuthFooter extends React.Component {
render() {
return (
<div className="mx_AuthFooter">
<a href="https://matrix.org" target="_blank" rel="noreferrer noopener">{ _t("powered by Matrix") }</a>
</div>
);
},
});
}
}

View file

@ -17,17 +17,14 @@ limitations under the License.
import React from 'react';
import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import * as sdk from '../../../index';
export default createReactClass({
displayName: 'AuthHeader',
propTypes: {
export default class AuthHeader extends React.Component {
static propTypes = {
disableLanguageSelector: PropTypes.bool,
},
};
render: function() {
render() {
const AuthHeaderLogo = sdk.getComponent('auth.AuthHeaderLogo');
const LanguageSelector = sdk.getComponent('views.auth.LanguageSelector');
@ -37,5 +34,5 @@ export default createReactClass({
<LanguageSelector disabled={this.props.disableLanguageSelector} />
</div>
);
},
});
}
}

View file

@ -15,7 +15,6 @@ limitations under the License.
*/
import React, {createRef} from 'react';
import createReactClass from 'create-react-class';
import PropTypes from 'prop-types';
import { _t } from '../../../languageHandler';
@ -24,36 +23,31 @@ const DIV_ID = 'mx_recaptcha';
/**
* A pure UI component which displays a captcha form.
*/
export default createReactClass({
displayName: 'CaptchaForm',
propTypes: {
export default class CaptchaForm extends React.Component {
static propTypes = {
sitePublicKey: PropTypes.string,
// called with the captcha response
onCaptchaResponse: PropTypes.func,
},
};
getDefaultProps: function() {
return {
onCaptchaResponse: () => {},
};
},
static defaultProps = {
onCaptchaResponse: () => {},
};
getInitialState: function() {
return {
constructor(props) {
super(props);
this.state = {
errorText: null,
};
},
// TODO: [REACT-WARNING] Replace component with real class, use constructor for refs
UNSAFE_componentWillMount: function() {
this._captchaWidgetId = null;
this._recaptchaContainer = createRef();
},
}
componentDidMount: function() {
componentDidMount() {
// Just putting a script tag into the returned jsx doesn't work, annoyingly,
// so we do this instead.
if (global.grecaptcha) {
@ -68,13 +62,13 @@ export default createReactClass({
);
this._recaptchaContainer.current.appendChild(scriptTag);
}
},
}
componentWillUnmount: function() {
componentWillUnmount() {
this._resetRecaptcha();
},
}
_renderRecaptcha: function(divId) {
_renderRecaptcha(divId) {
if (!global.grecaptcha) {
console.error("grecaptcha not loaded!");
throw new Error("Recaptcha did not load successfully");
@ -93,15 +87,15 @@ export default createReactClass({
sitekey: publicKey,
callback: this.props.onCaptchaResponse,
});
},
}
_resetRecaptcha: function() {
_resetRecaptcha() {
if (this._captchaWidgetId !== null) {
global.grecaptcha.reset(this._captchaWidgetId);
}
},
}
_onCaptchaLoaded: function() {
_onCaptchaLoaded() {
console.log("Loaded recaptcha script.");
try {
this._renderRecaptcha(DIV_ID);
@ -110,9 +104,9 @@ export default createReactClass({
errorText: e.toString(),
});
}
},
}
render: function() {
render() {
let error = null;
if (this.state.errorText) {
error = (
@ -131,5 +125,5 @@ export default createReactClass({
{ error }
</div>
);
},
});
}
}

View file

@ -16,14 +16,11 @@ limitations under the License.
*/
import React from 'react';
import createReactClass from 'create-react-class';
import { _t } from '../../../languageHandler';
import SdkConfig from '../../../SdkConfig';
export default createReactClass({
displayName: 'CustomServerDialog',
render: function() {
export default class CustomServerDialog extends React.Component {
render() {
const brand = SdkConfig.get().brand;
return (
<div className="mx_ErrorDialog">
@ -46,5 +43,5 @@ export default createReactClass({
</div>
</div>
);
},
});
}
}

View file

@ -17,7 +17,6 @@ limitations under the License.
*/
import React, {createRef} from 'react';
import createReactClass from 'create-react-class';
import PropTypes from 'prop-types';
import url from 'url';
import classnames from 'classnames';
@ -75,14 +74,10 @@ import AccessibleButton from "../elements/AccessibleButton";
export const DEFAULT_PHASE = 0;
export const PasswordAuthEntry = createReactClass({
displayName: 'PasswordAuthEntry',
export class PasswordAuthEntry extends React.Component {
static LOGIN_TYPE = "m.login.password";
statics: {
LOGIN_TYPE: "m.login.password",
},
propTypes: {
static propTypes = {
matrixClient: PropTypes.object.isRequired,
submitAuthDict: PropTypes.func.isRequired,
errorText: PropTypes.string,
@ -90,19 +85,17 @@ export const PasswordAuthEntry = createReactClass({
// happen?
busy: PropTypes.bool,
onPhaseChange: PropTypes.func.isRequired,
},
};
componentDidMount: function() {
componentDidMount() {
this.props.onPhaseChange(DEFAULT_PHASE);
},
}
getInitialState: function() {
return {
password: "",
};
},
state = {
password: "",
};
_onSubmit: function(e) {
_onSubmit = e => {
e.preventDefault();
if (this.props.busy) return;
@ -117,16 +110,16 @@ export const PasswordAuthEntry = createReactClass({
},
password: this.state.password,
});
},
};
_onPasswordFieldChange: function(ev) {
_onPasswordFieldChange = ev => {
// enable the submit button iff the password is non-empty
this.setState({
password: ev.target.value,
});
},
};
render: function() {
render() {
const passwordBoxClass = classnames({
"error": this.props.errorText,
});
@ -176,36 +169,32 @@ export const PasswordAuthEntry = createReactClass({
{ errorSection }
</div>
);
},
});
}
}
export const RecaptchaAuthEntry = createReactClass({
displayName: 'RecaptchaAuthEntry',
export class RecaptchaAuthEntry extends React.Component {
static LOGIN_TYPE = "m.login.recaptcha";
statics: {
LOGIN_TYPE: "m.login.recaptcha",
},
propTypes: {
static propTypes = {
submitAuthDict: PropTypes.func.isRequired,
stageParams: PropTypes.object.isRequired,
errorText: PropTypes.string,
busy: PropTypes.bool,
onPhaseChange: PropTypes.func.isRequired,
},
};
componentDidMount: function() {
componentDidMount() {
this.props.onPhaseChange(DEFAULT_PHASE);
},
}
_onCaptchaResponse: function(response) {
_onCaptchaResponse = response => {
this.props.submitAuthDict({
type: RecaptchaAuthEntry.LOGIN_TYPE,
response: response,
});
},
};
render: function() {
render() {
if (this.props.busy) {
const Loader = sdk.getComponent("elements.Spinner");
return <Loader />;
@ -241,31 +230,24 @@ export const RecaptchaAuthEntry = createReactClass({
{ errorSection }
</div>
);
},
});
}
}
export const TermsAuthEntry = createReactClass({
displayName: 'TermsAuthEntry',
export class TermsAuthEntry extends React.Component {
static LOGIN_TYPE = "m.login.terms";
statics: {
LOGIN_TYPE: "m.login.terms",
},
propTypes: {
static propTypes = {
submitAuthDict: PropTypes.func.isRequired,
stageParams: PropTypes.object.isRequired,
errorText: PropTypes.string,
busy: PropTypes.bool,
showContinue: PropTypes.bool,
onPhaseChange: PropTypes.func.isRequired,
},
};
componentDidMount: function() {
this.props.onPhaseChange(DEFAULT_PHASE);
},
constructor(props) {
super(props);
// TODO: [REACT-WARNING] Move this to constructor
componentWillMount: function() {
// example stageParams:
//
// {
@ -310,17 +292,22 @@ export const TermsAuthEntry = createReactClass({
pickedPolicies.push(langPolicy);
}
this.setState({
"toggledPolicies": initToggles,
"policies": pickedPolicies,
});
},
this.state = {
toggledPolicies: initToggles,
policies: pickedPolicies,
};
}
tryContinue: function() {
componentDidMount() {
this.props.onPhaseChange(DEFAULT_PHASE);
}
tryContinue = () => {
this._trySubmit();
},
};
_togglePolicy: function(policyId) {
_togglePolicy(policyId) {
const newToggles = {};
for (const policy of this.state.policies) {
let checked = this.state.toggledPolicies[policy.id];
@ -329,9 +316,9 @@ export const TermsAuthEntry = createReactClass({
newToggles[policy.id] = checked;
}
this.setState({"toggledPolicies": newToggles});
},
}
_trySubmit: function() {
_trySubmit = () => {
let allChecked = true;
for (const policy of this.state.policies) {
const checked = this.state.toggledPolicies[policy.id];
@ -340,9 +327,9 @@ export const TermsAuthEntry = createReactClass({
if (allChecked) this.props.submitAuthDict({type: TermsAuthEntry.LOGIN_TYPE});
else this.setState({errorText: _t("Please review and accept all of the homeserver's policies")});
},
};
render: function() {
render() {
if (this.props.busy) {
const Loader = sdk.getComponent("elements.Spinner");
return <Loader />;
@ -387,17 +374,13 @@ export const TermsAuthEntry = createReactClass({
{ submitButton }
</div>
);
},
});
}
}
export const EmailIdentityAuthEntry = createReactClass({
displayName: 'EmailIdentityAuthEntry',
export class EmailIdentityAuthEntry extends React.Component {
static LOGIN_TYPE = "m.login.email.identity";
statics: {
LOGIN_TYPE: "m.login.email.identity",
},
propTypes: {
static propTypes = {
matrixClient: PropTypes.object.isRequired,
submitAuthDict: PropTypes.func.isRequired,
authSessionId: PropTypes.string.isRequired,
@ -407,13 +390,13 @@ export const EmailIdentityAuthEntry = createReactClass({
fail: PropTypes.func.isRequired,
setEmailSid: PropTypes.func.isRequired,
onPhaseChange: PropTypes.func.isRequired,
},
};
componentDidMount: function() {
componentDidMount() {
this.props.onPhaseChange(DEFAULT_PHASE);
},
}
render: function() {
render() {
// This component is now only displayed once the token has been requested,
// so we know the email has been sent. It can also get loaded after the user
// has clicked the validation link if the server takes a while to propagate
@ -434,17 +417,13 @@ export const EmailIdentityAuthEntry = createReactClass({
</div>
);
}
},
});
}
}
export const MsisdnAuthEntry = createReactClass({
displayName: 'MsisdnAuthEntry',
export class MsisdnAuthEntry extends React.Component {
static LOGIN_TYPE = "m.login.msisdn";
statics: {
LOGIN_TYPE: "m.login.msisdn",
},
propTypes: {
static propTypes = {
inputs: PropTypes.shape({
phoneCountry: PropTypes.string,
phoneNumber: PropTypes.string,
@ -454,16 +433,14 @@ export const MsisdnAuthEntry = createReactClass({
submitAuthDict: PropTypes.func.isRequired,
matrixClient: PropTypes.object,
onPhaseChange: PropTypes.func.isRequired,
},
};
getInitialState: function() {
return {
token: '',
requestingToken: false,
};
},
state = {
token: '',
requestingToken: false,
};
componentDidMount: function() {
componentDidMount() {
this.props.onPhaseChange(DEFAULT_PHASE);
this._submitUrl = null;
@ -477,12 +454,12 @@ export const MsisdnAuthEntry = createReactClass({
}).finally(() => {
this.setState({requestingToken: false});
});
},
}
/*
* Requests a verification token by SMS.
*/
_requestMsisdnToken: function() {
_requestMsisdnToken() {
return this.props.matrixClient.requestRegisterMsisdnToken(
this.props.inputs.phoneCountry,
this.props.inputs.phoneNumber,
@ -493,15 +470,15 @@ export const MsisdnAuthEntry = createReactClass({
this._sid = result.sid;
this._msisdn = result.msisdn;
});
},
}
_onTokenChange: function(e) {
_onTokenChange = e => {
this.setState({
token: e.target.value,
});
},
};
_onFormSubmit: async function(e) {
_onFormSubmit = async e => {
e.preventDefault();
if (this.state.token == '') return;
@ -552,9 +529,9 @@ export const MsisdnAuthEntry = createReactClass({
this.props.fail(e);
console.log("Failed to submit msisdn token");
}
},
};
render: function() {
render() {
if (this.state.requestingToken) {
const Loader = sdk.getComponent("elements.Spinner");
return <Loader />;
@ -598,8 +575,8 @@ export const MsisdnAuthEntry = createReactClass({
</div>
);
}
},
});
}
}
export class SSOAuthEntry extends React.Component {
static propTypes = {
@ -686,46 +663,46 @@ export class SSOAuthEntry extends React.Component {
}
}
export const FallbackAuthEntry = createReactClass({
displayName: 'FallbackAuthEntry',
propTypes: {
export class FallbackAuthEntry extends React.Component {
static propTypes = {
matrixClient: PropTypes.object.isRequired,
authSessionId: PropTypes.string.isRequired,
loginType: PropTypes.string.isRequired,
submitAuthDict: PropTypes.func.isRequired,
errorText: PropTypes.string,
onPhaseChange: PropTypes.func.isRequired,
},
};
componentDidMount: function() {
this.props.onPhaseChange(DEFAULT_PHASE);
},
constructor(props) {
super(props);
// TODO: [REACT-WARNING] Replace component with real class, use constructor for refs
UNSAFE_componentWillMount: function() {
// we have to make the user click a button, as browsers will block
// the popup if we open it immediately.
this._popupWindow = null;
window.addEventListener("message", this._onReceiveMessage);
this._fallbackButton = createRef();
},
}
componentWillUnmount: function() {
componentDidMount() {
this.props.onPhaseChange(DEFAULT_PHASE);
}
componentWillUnmount() {
window.removeEventListener("message", this._onReceiveMessage);
if (this._popupWindow) {
this._popupWindow.close();
}
},
}
focus: function() {
focus = () => {
if (this._fallbackButton.current) {
this._fallbackButton.current.focus();
}
},
};
_onShowFallbackClick: function(e) {
_onShowFallbackClick = e => {
e.preventDefault();
e.stopPropagation();
@ -735,18 +712,18 @@ export const FallbackAuthEntry = createReactClass({
);
this._popupWindow = window.open(url);
this._popupWindow.opener = null;
},
};
_onReceiveMessage: function(event) {
_onReceiveMessage = event => {
if (
event.data === "authDone" &&
event.origin === this.props.matrixClient.getHomeserverUrl()
) {
this.props.submitAuthDict({});
}
},
};
render: function() {
render() {
let errorSection;
if (this.props.errorText) {
errorSection = (
@ -761,8 +738,8 @@ export const FallbackAuthEntry = createReactClass({
{errorSection}
</div>
);
},
});
}
}
const AuthEntryComponents = [
PasswordAuthEntry,

View file

@ -18,7 +18,6 @@ limitations under the License.
*/
import React from 'react';
import createReactClass from 'create-react-class';
import PropTypes from 'prop-types';
import * as sdk from '../../../index';
import * as Email from '../../../email';
@ -39,13 +38,11 @@ const FIELD_PASSWORD_CONFIRM = 'field_password_confirm';
const PASSWORD_MIN_SCORE = 3; // safely unguessable: moderate protection from offline slow-hash scenario.
/**
/*
* A pure UI component which displays a registration form.
*/
export default createReactClass({
displayName: 'RegistrationForm',
propTypes: {
export default class RegistrationForm extends React.Component {
static propTypes = {
// Values pre-filled in the input boxes when the component loads
defaultEmail: PropTypes.string,
defaultPhoneCountry: PropTypes.string,
@ -58,17 +55,17 @@ export default createReactClass({
serverConfig: PropTypes.instanceOf(ValidatedServerConfig).isRequired,
canSubmit: PropTypes.bool,
serverRequiresIdServer: PropTypes.bool,
},
};
getDefaultProps: function() {
return {
onValidationChange: console.error,
canSubmit: true,
};
},
static defaultProps = {
onValidationChange: console.error,
canSubmit: true,
};
getInitialState: function() {
return {
constructor(props) {
super(props);
this.state = {
// Field error codes by field ID
fieldValid: {},
// The ISO2 country code selected in the phone number entry
@ -80,9 +77,9 @@ export default createReactClass({
passwordConfirm: this.props.defaultPassword || "",
passwordComplexity: null,
};
},
}
onSubmit: async function(ev) {
onSubmit = async ev => {
ev.preventDefault();
if (!this.props.canSubmit) return;
@ -118,7 +115,7 @@ export default createReactClass({
title: _t("Warning!"),
description: desc,
button: _t("Continue"),
onFinished: function(confirmed) {
onFinished(confirmed) {
if (confirmed) {
self._doSubmit(ev);
}
@ -127,9 +124,9 @@ export default createReactClass({
} else {
self._doSubmit(ev);
}
},
};
_doSubmit: function(ev) {
_doSubmit(ev) {
const email = this.state.email.trim();
const promise = this.props.onRegisterClick({
username: this.state.username.trim(),
@ -145,7 +142,7 @@ export default createReactClass({
ev.target.disabled = false;
});
}
},
}
async verifyFieldsBeforeSubmit() {
// Blur the active element if any, so we first run its blur validation,
@ -196,12 +193,12 @@ export default createReactClass({
invalidField.focus();
invalidField.validate({ allowEmpty: false, focused: true });
return false;
},
}
/**
* @returns {boolean} true if all fields were valid last time they were validated.
*/
allFieldsValid: function() {
allFieldsValid() {
const keys = Object.keys(this.state.fieldValid);
for (let i = 0; i < keys.length; ++i) {
if (!this.state.fieldValid[keys[i]]) {
@ -209,7 +206,7 @@ export default createReactClass({
}
}
return true;
},
}
findFirstInvalidField(fieldIDs) {
for (const fieldID of fieldIDs) {
@ -218,34 +215,34 @@ export default createReactClass({
}
}
return null;
},
}
markFieldValid: function(fieldID, valid) {
markFieldValid(fieldID, valid) {
const { fieldValid } = this.state;
fieldValid[fieldID] = valid;
this.setState({
fieldValid,
});
},
}
onEmailChange(ev) {
onEmailChange = ev => {
this.setState({
email: ev.target.value,
});
},
};
async onEmailValidate(fieldState) {
onEmailValidate = async fieldState => {
const result = await this.validateEmailRules(fieldState);
this.markFieldValid(FIELD_EMAIL, result.valid);
return result;
},
};
validateEmailRules: withValidation({
validateEmailRules = withValidation({
description: () => _t("Use an email address to recover your account"),
rules: [
{
key: "required",
test: function({ value, allowEmpty }) {
test({ value, allowEmpty }) {
return allowEmpty || !this._authStepIsRequired('m.login.email.identity') || !!value;
},
invalid: () => _t("Enter email address (required on this homeserver)"),
@ -256,31 +253,31 @@ export default createReactClass({
invalid: () => _t("Doesn't look like a valid email address"),
},
],
}),
});
onPasswordChange(ev) {
onPasswordChange = ev => {
this.setState({
password: ev.target.value,
});
},
};
onPasswordValidate(result) {
onPasswordValidate = result => {
this.markFieldValid(FIELD_PASSWORD, result.valid);
},
};
onPasswordConfirmChange(ev) {
onPasswordConfirmChange = ev => {
this.setState({
passwordConfirm: ev.target.value,
});
},
};
async onPasswordConfirmValidate(fieldState) {
onPasswordConfirmValidate = async fieldState => {
const result = await this.validatePasswordConfirmRules(fieldState);
this.markFieldValid(FIELD_PASSWORD_CONFIRM, result.valid);
return result;
},
};
validatePasswordConfirmRules: withValidation({
validatePasswordConfirmRules = withValidation({
rules: [
{
key: "required",
@ -289,39 +286,39 @@ export default createReactClass({
},
{
key: "match",
test: function({ value }) {
test({ value }) {
return !value || value === this.state.password;
},
invalid: () => _t("Passwords don't match"),
},
],
}),
});
onPhoneCountryChange(newVal) {
onPhoneCountryChange = newVal => {
this.setState({
phoneCountry: newVal.iso2,
phonePrefix: newVal.prefix,
});
},
};
onPhoneNumberChange(ev) {
onPhoneNumberChange = ev => {
this.setState({
phoneNumber: ev.target.value,
});
},
};
async onPhoneNumberValidate(fieldState) {
onPhoneNumberValidate = async fieldState => {
const result = await this.validatePhoneNumberRules(fieldState);
this.markFieldValid(FIELD_PHONE_NUMBER, result.valid);
return result;
},
};
validatePhoneNumberRules: withValidation({
validatePhoneNumberRules = withValidation({
description: () => _t("Other users can invite you to rooms using your contact details"),
rules: [
{
key: "required",
test: function({ value, allowEmpty }) {
test({ value, allowEmpty }) {
return allowEmpty || !this._authStepIsRequired('m.login.msisdn') || !!value;
},
invalid: () => _t("Enter phone number (required on this homeserver)"),
@ -332,21 +329,21 @@ export default createReactClass({
invalid: () => _t("Doesn't look like a valid phone number"),
},
],
}),
});
onUsernameChange(ev) {
onUsernameChange = ev => {
this.setState({
username: ev.target.value,
});
},
};
async onUsernameValidate(fieldState) {
onUsernameValidate = async fieldState => {
const result = await this.validateUsernameRules(fieldState);
this.markFieldValid(FIELD_USERNAME, result.valid);
return result;
},
};
validateUsernameRules: withValidation({
validateUsernameRules = withValidation({
description: () => _t("Use lowercase letters, numbers, dashes and underscores only"),
rules: [
{
@ -360,7 +357,7 @@ export default createReactClass({
invalid: () => _t("Some characters not allowed"),
},
],
}),
});
/**
* A step is required if all flows include that step.
@ -372,7 +369,7 @@ export default createReactClass({
return this.props.flows.every((flow) => {
return flow.stages.includes(step);
});
},
}
/**
* A step is used if any flows include that step.
@ -384,7 +381,7 @@ export default createReactClass({
return this.props.flows.some((flow) => {
return flow.stages.includes(step);
});
},
}
_showEmail() {
const haveIs = Boolean(this.props.serverConfig.isUrl);
@ -395,7 +392,7 @@ export default createReactClass({
return false;
}
return true;
},
}
_showPhoneNumber() {
const threePidLogin = !SdkConfig.get().disable_3pid_login;
@ -408,7 +405,7 @@ export default createReactClass({
return false;
}
return true;
},
}
renderEmail() {
if (!this._showEmail()) {
@ -426,7 +423,7 @@ export default createReactClass({
onChange={this.onEmailChange}
onValidate={this.onEmailValidate}
/>;
},
}
renderPassword() {
return <PassphraseField
@ -437,7 +434,7 @@ export default createReactClass({
onChange={this.onPasswordChange}
onValidate={this.onPasswordValidate}
/>;
},
}
renderPasswordConfirm() {
const Field = sdk.getComponent('elements.Field');
@ -451,7 +448,7 @@ export default createReactClass({
onChange={this.onPasswordConfirmChange}
onValidate={this.onPasswordConfirmValidate}
/>;
},
}
renderPhoneNumber() {
if (!this._showPhoneNumber()) {
@ -477,7 +474,7 @@ export default createReactClass({
onChange={this.onPhoneNumberChange}
onValidate={this.onPhoneNumberValidate}
/>;
},
}
renderUsername() {
const Field = sdk.getComponent('elements.Field');
@ -491,9 +488,9 @@ export default createReactClass({
onChange={this.onUsernameChange}
onValidate={this.onUsernameValidate}
/>;
},
}
render: function() {
render() {
let yourMatrixAccountText = _t('Create your Matrix account on %(serverName)s', {
serverName: this.props.serverConfig.hsName,
});
@ -578,5 +575,5 @@ export default createReactClass({
</form>
</div>
);
},
});
}
}

View file

@ -16,23 +16,24 @@ limitations under the License.
*/
import React from 'react';
import {RoomMember} from "matrix-js-sdk/src/models/room-member";
import dis from "../../../dispatcher/dispatcher";
import {Action} from "../../../dispatcher/actions";
import {MatrixClientPeg} from "../../../MatrixClientPeg";
import BaseAvatar from "./BaseAvatar";
interface IProps {
// TODO: replace with correct type
member: any;
fallbackUserId: string;
member: RoomMember;
fallbackUserId?: string;
width: number;
height: number;
resizeMethod: string;
resizeMethod?: string;
// The onClick to give the avatar
onClick: React.MouseEventHandler;
onClick?: React.MouseEventHandler;
// Whether the onClick of the avatar should be overriden to dispatch `Action.ViewUser`
viewUserOnClick: boolean;
title: string;
viewUserOnClick?: boolean;
title?: string;
}
interface IState {

View file

@ -19,7 +19,6 @@ limitations under the License.
import React from 'react';
import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import {EventStatus} from 'matrix-js-sdk';
import {MatrixClientPeg} from '../../../MatrixClientPeg';
@ -37,10 +36,8 @@ function canCancel(eventStatus) {
return eventStatus === EventStatus.QUEUED || eventStatus === EventStatus.NOT_SENT;
}
export default createReactClass({
displayName: 'MessageContextMenu',
propTypes: {
export default class MessageContextMenu extends React.Component {
static propTypes = {
/* the MatrixEvent associated with the context menu */
mxEvent: PropTypes.object.isRequired,
@ -52,28 +49,26 @@ export default createReactClass({
/* callback called when the menu is dismissed */
onFinished: PropTypes.func,
},
};
getInitialState: function() {
return {
canRedact: false,
canPin: false,
};
},
state = {
canRedact: false,
canPin: false,
};
componentDidMount: function() {
componentDidMount() {
MatrixClientPeg.get().on('RoomMember.powerLevel', this._checkPermissions);
this._checkPermissions();
},
}
componentWillUnmount: function() {
componentWillUnmount() {
const cli = MatrixClientPeg.get();
if (cli) {
cli.removeListener('RoomMember.powerLevel', this._checkPermissions);
}
},
}
_checkPermissions: function() {
_checkPermissions = () => {
const cli = MatrixClientPeg.get();
const room = cli.getRoom(this.props.mxEvent.getRoomId());
@ -84,47 +79,47 @@ export default createReactClass({
if (!SettingsStore.getValue("feature_pinning")) canPin = false;
this.setState({canRedact, canPin});
},
};
_isPinned: function() {
_isPinned() {
const room = MatrixClientPeg.get().getRoom(this.props.mxEvent.getRoomId());
const pinnedEvent = room.currentState.getStateEvents('m.room.pinned_events', '');
if (!pinnedEvent) return false;
const content = pinnedEvent.getContent();
return content.pinned && Array.isArray(content.pinned) && content.pinned.includes(this.props.mxEvent.getId());
},
}
onResendClick: function() {
onResendClick = () => {
Resend.resend(this.props.mxEvent);
this.closeMenu();
},
};
onResendEditClick: function() {
onResendEditClick = () => {
Resend.resend(this.props.mxEvent.replacingEvent());
this.closeMenu();
},
};
onResendRedactionClick: function() {
onResendRedactionClick = () => {
Resend.resend(this.props.mxEvent.localRedactionEvent());
this.closeMenu();
},
};
onResendReactionsClick: function() {
onResendReactionsClick = () => {
for (const reaction of this._getUnsentReactions()) {
Resend.resend(reaction);
}
this.closeMenu();
},
};
onReportEventClick: function() {
onReportEventClick = () => {
const ReportEventDialog = sdk.getComponent("dialogs.ReportEventDialog");
Modal.createTrackedDialog('Report Event', '', ReportEventDialog, {
mxEvent: this.props.mxEvent,
}, 'mx_Dialog_reportEvent');
this.closeMenu();
},
};
onViewSourceClick: function() {
onViewSourceClick = () => {
const ev = this.props.mxEvent.replacingEvent() || this.props.mxEvent;
const ViewSource = sdk.getComponent('structures.ViewSource');
Modal.createTrackedDialog('View Event Source', '', ViewSource, {
@ -133,9 +128,9 @@ export default createReactClass({
content: ev.event,
}, 'mx_Dialog_viewsource');
this.closeMenu();
},
};
onViewClearSourceClick: function() {
onViewClearSourceClick = () => {
const ev = this.props.mxEvent.replacingEvent() || this.props.mxEvent;
const ViewSource = sdk.getComponent('structures.ViewSource');
Modal.createTrackedDialog('View Clear Event Source', '', ViewSource, {
@ -145,9 +140,9 @@ export default createReactClass({
content: ev._clearEvent,
}, 'mx_Dialog_viewsource');
this.closeMenu();
},
};
onRedactClick: function() {
onRedactClick = () => {
const ConfirmRedactDialog = sdk.getComponent("dialogs.ConfirmRedactDialog");
Modal.createTrackedDialog('Confirm Redact Dialog', '', ConfirmRedactDialog, {
onFinished: async (proceed) => {
@ -176,9 +171,9 @@ export default createReactClass({
},
}, 'mx_Dialog_confirmredact');
this.closeMenu();
},
};
onCancelSendClick: function() {
onCancelSendClick = () => {
const mxEvent = this.props.mxEvent;
const editEvent = mxEvent.replacingEvent();
const redactEvent = mxEvent.localRedactionEvent();
@ -199,17 +194,17 @@ export default createReactClass({
Resend.removeFromQueue(this.props.mxEvent);
}
this.closeMenu();
},
};
onForwardClick: function() {
onForwardClick = () => {
dis.dispatch({
action: 'forward_event',
event: this.props.mxEvent,
});
this.closeMenu();
},
};
onPinClick: function() {
onPinClick = () => {
MatrixClientPeg.get().getStateEvent(this.props.mxEvent.getRoomId(), 'm.room.pinned_events', '')
.catch((e) => {
// Intercept the Event Not Found error and fall through the promise chain with no event.
@ -230,28 +225,28 @@ export default createReactClass({
cli.sendStateEvent(this.props.mxEvent.getRoomId(), 'm.room.pinned_events', {pinned: eventIds}, '');
});
this.closeMenu();
},
};
closeMenu: function() {
closeMenu = () => {
if (this.props.onFinished) this.props.onFinished();
},
};
onUnhidePreviewClick: function() {
onUnhidePreviewClick = () => {
if (this.props.eventTileOps) {
this.props.eventTileOps.unhideWidget();
}
this.closeMenu();
},
};
onQuoteClick: function() {
onQuoteClick = () => {
dis.dispatch({
action: 'quote',
event: this.props.mxEvent,
});
this.closeMenu();
},
};
onPermalinkClick: function(e: Event) {
onPermalinkClick = (e: Event) => {
e.preventDefault();
const ShareDialog = sdk.getComponent("dialogs.ShareDialog");
Modal.createTrackedDialog('share room message dialog', '', ShareDialog, {
@ -259,12 +254,12 @@ export default createReactClass({
permalinkCreator: this.props.permalinkCreator,
});
this.closeMenu();
},
};
onCollapseReplyThreadClick: function() {
onCollapseReplyThreadClick = () => {
this.props.collapseReplyThread();
this.closeMenu();
},
};
_getReactions(filter) {
const cli = MatrixClientPeg.get();
@ -277,17 +272,17 @@ export default createReactClass({
relation.event_id === eventId &&
filter(e);
});
},
}
_getPendingReactions() {
return this._getReactions(e => canCancel(e.status));
},
}
_getUnsentReactions() {
return this._getReactions(e => e.status === EventStatus.NOT_SENT);
},
}
render: function() {
render() {
const cli = MatrixClientPeg.get();
const me = cli.getUserId();
const mxEvent = this.props.mxEvent;
@ -489,5 +484,5 @@ export default createReactClass({
{ reportEventButton }
</div>
);
},
});
}
}

View file

@ -19,7 +19,6 @@ limitations under the License.
import React, {createRef} from 'react';
import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import { _t, _td } from '../../../languageHandler';
import * as sdk from '../../../index';
@ -45,10 +44,8 @@ const addressTypeName = {
};
export default createReactClass({
displayName: "AddressPickerDialog",
propTypes: {
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
@ -66,26 +63,28 @@ export default createReactClass({
// Whether the current user should be included in the addresses returned. Only
// applicable when pickerType is `user`. Default: false.
includeSelf: PropTypes.bool,
},
};
getDefaultProps: function() {
return {
value: "",
focus: true,
validAddressTypes: addressTypes,
pickerType: 'user',
includeSelf: false,
};
},
static defaultProps = {
value: "",
focus: true,
validAddressTypes: addressTypes,
pickerType: 'user',
includeSelf: false,
};
constructor(props) {
super(props);
this._textinput = createRef();
getInitialState: function() {
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");
}
return {
this.state = {
// Whether to show an error message because of an invalid address
invalidAddressError: false,
// List of UserAddressType objects representing
@ -106,19 +105,14 @@ export default createReactClass({
// dialog is open and represents the supported list of address types at this time.
validAddressTypes,
};
},
}
// TODO: [REACT-WARNING] Replace component with real class, use constructor for refs
UNSAFE_componentWillMount: function() {
this._textinput = createRef();
},
componentDidMount: function() {
componentDidMount() {
if (this.props.focus) {
// Set the cursor at the end of the text input
this._textinput.current.value = this.props.value;
}
},
}
getPlaceholder() {
const { placeholder } = this.props;
@ -127,9 +121,9 @@ export default createReactClass({
}
// Otherwise it's a function, as checked by prop types.
return placeholder(this.state.validAddressTypes);
},
}
onButtonClick: function() {
onButtonClick = () => {
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
@ -138,13 +132,13 @@ export default createReactClass({
if (selectedList === null) return;
}
this.props.onFinished(true, selectedList);
},
};
onCancel: function() {
onCancel = () => {
this.props.onFinished(false);
},
};
onKeyDown: function(e) {
onKeyDown = e => {
const textInput = this._textinput.current ? this._textinput.current.value : undefined;
if (e.key === Key.ESCAPE) {
@ -181,9 +175,9 @@ export default createReactClass({
e.preventDefault();
this._addAddressesToList([textInput]);
}
},
};
onQueryChanged: function(ev) {
onQueryChanged = ev => {
const query = ev.target.value;
if (this.queryChangedDebouncer) {
clearTimeout(this.queryChangedDebouncer);
@ -216,28 +210,24 @@ export default createReactClass({
searchError: null,
});
}
},
};
onDismissed: function(index) {
return () => {
const selectedList = this.state.selectedList.slice();
selectedList.splice(index, 1);
this.setState({
selectedList,
suggestedList: [],
query: "",
});
if (this._cancelThreepidLookup) this._cancelThreepidLookup();
};
},
onDismissed = index => () => {
const selectedList = this.state.selectedList.slice();
selectedList.splice(index, 1);
this.setState({
selectedList,
suggestedList: [],
query: "",
});
if (this._cancelThreepidLookup) this._cancelThreepidLookup();
};
onClick: function(index) {
return () => {
this.onSelected(index);
};
},
onClick = index => () => {
this.onSelected(index);
};
onSelected: function(index) {
onSelected = index => {
const selectedList = this.state.selectedList.slice();
selectedList.push(this._getFilteredSuggestions()[index]);
this.setState({
@ -246,9 +236,9 @@ export default createReactClass({
query: "",
});
if (this._cancelThreepidLookup) this._cancelThreepidLookup();
},
};
_doNaiveGroupSearch: function(query) {
_doNaiveGroupSearch(query) {
const lowerCaseQuery = query.toLowerCase();
this.setState({
busy: true,
@ -280,9 +270,9 @@ export default createReactClass({
busy: false,
});
});
},
}
_doNaiveGroupRoomSearch: function(query) {
_doNaiveGroupRoomSearch(query) {
const lowerCaseQuery = query.toLowerCase();
const results = [];
GroupStore.getGroupRooms(this.props.groupId).forEach((r) => {
@ -302,9 +292,9 @@ export default createReactClass({
this.setState({
busy: false,
});
},
}
_doRoomSearch: function(query) {
_doRoomSearch(query) {
const lowerCaseQuery = query.toLowerCase();
const rooms = MatrixClientPeg.get().getRooms();
const results = [];
@ -359,9 +349,9 @@ export default createReactClass({
this.setState({
busy: false,
});
},
}
_doUserDirectorySearch: function(query) {
_doUserDirectorySearch(query) {
this.setState({
busy: true,
query,
@ -393,9 +383,9 @@ export default createReactClass({
busy: false,
});
});
},
}
_doLocalSearch: function(query) {
_doLocalSearch(query) {
this.setState({
query,
searchError: null,
@ -417,9 +407,9 @@ export default createReactClass({
});
});
this._processResults(results, query);
},
}
_processResults: function(results, query) {
_processResults(results, query) {
const suggestedList = [];
results.forEach((result) => {
if (result.room_id) {
@ -485,9 +475,9 @@ export default createReactClass({
}, () => {
if (this.addressSelector) this.addressSelector.moveSelectionTop();
});
},
}
_addAddressesToList: function(addressTexts) {
_addAddressesToList(addressTexts) {
const selectedList = this.state.selectedList.slice();
let hasError = false;
@ -529,9 +519,9 @@ export default createReactClass({
});
if (this._cancelThreepidLookup) this._cancelThreepidLookup();
return hasError ? null : selectedList;
},
}
_lookupThreepid: async function(medium, address) {
async _lookupThreepid(medium, address) {
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
@ -577,9 +567,9 @@ export default createReactClass({
searchError: _t('Something went wrong!'),
});
}
},
}
_getFilteredSuggestions: function() {
_getFilteredSuggestions() {
// map addressType => set of addresses to avoid O(n*m) operation
const selectedAddresses = {};
this.state.selectedList.forEach(({address, addressType}) => {
@ -591,17 +581,17 @@ export default createReactClass({
return this.state.suggestedList.filter(({address, addressType}) => {
return !(selectedAddresses[addressType] && selectedAddresses[addressType].has(address));
});
},
}
_onPaste: function(e) {
_onPaste = e => {
// 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,]+/));
},
};
onUseDefaultIdentityServerClick(e) {
onUseDefaultIdentityServerClick = e => {
e.preventDefault();
// Update the IS in account data. Actually using it may trigger terms.
@ -612,15 +602,15 @@ export default createReactClass({
const { validAddressTypes } = this.state;
validAddressTypes.push('email');
this.setState({ validAddressTypes });
},
};
onManageSettingsClick(e) {
onManageSettingsClick = e => {
e.preventDefault();
dis.fire(Action.ViewUserSettings);
this.onCancel();
},
};
render: function() {
render() {
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
const AddressSelector = sdk.getComponent("elements.AddressSelector");
@ -738,5 +728,5 @@ export default createReactClass({
onCancel={this.onCancel} />
</BaseDialog>
);
},
});
}
}

View file

@ -16,37 +16,36 @@ limitations under the License.
import React from 'react';
import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import * as sdk from '../../../index';
import { _t } from '../../../languageHandler';
import SettingsStore from "../../../settings/SettingsStore";
import {SettingLevel} from "../../../settings/SettingLevel";
export default createReactClass({
propTypes: {
export default class AskInviteAnywayDialog extends React.Component {
static propTypes = {
unknownProfileUsers: PropTypes.array.isRequired, // [ {userId, errorText}... ]
onInviteAnyways: PropTypes.func.isRequired,
onGiveUp: PropTypes.func.isRequired,
onFinished: PropTypes.func.isRequired,
},
};
_onInviteClicked: function() {
_onInviteClicked = () => {
this.props.onInviteAnyways();
this.props.onFinished(true);
},
};
_onInviteNeverWarnClicked: function() {
_onInviteNeverWarnClicked = () => {
SettingsStore.setValue("promptBeforeInviteUnknownUsers", null, SettingLevel.ACCOUNT, false);
this.props.onInviteAnyways();
this.props.onFinished(true);
},
};
_onGiveUpClicked: function() {
_onGiveUpClicked = () => {
this.props.onGiveUp();
this.props.onFinished(false);
},
};
render: function() {
render() {
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
const errorList = this.props.unknownProfileUsers
@ -78,5 +77,5 @@ export default createReactClass({
</div>
</BaseDialog>
);
},
});
}
}

View file

@ -17,7 +17,6 @@ limitations under the License.
*/
import React from 'react';
import createReactClass from 'create-react-class';
import FocusLock from 'react-focus-lock';
import PropTypes from 'prop-types';
import classNames from 'classnames';
@ -28,16 +27,14 @@ import {MatrixClientPeg} from '../../../MatrixClientPeg';
import { _t } from "../../../languageHandler";
import MatrixClientContext from "../../../contexts/MatrixClientContext";
/**
/*
* Basic container for modal dialogs.
*
* Includes a div for the title, and a keypress handler which cancels the
* dialog on escape.
*/
export default createReactClass({
displayName: 'BaseDialog',
propTypes: {
export default class BaseDialog extends React.Component {
static propTypes = {
// onFinished callback to call when Escape is pressed
// Take a boolean which is true if the dialog was dismissed
// with a positive / confirm action or false if it was
@ -81,21 +78,20 @@ export default createReactClass({
PropTypes.object,
PropTypes.arrayOf(PropTypes.string),
]),
},
};
getDefaultProps: function() {
return {
hasCancel: true,
fixedWidth: true,
};
},
static defaultProps = {
hasCancel: true,
fixedWidth: true,
};
constructor(props) {
super(props);
// TODO: [REACT-WARNING] Move this to constructor
UNSAFE_componentWillMount() {
this._matrixClient = MatrixClientPeg.get();
},
}
_onKeyDown: function(e) {
_onKeyDown = (e) => {
if (this.props.onKeyDown) {
this.props.onKeyDown(e);
}
@ -104,13 +100,13 @@ export default createReactClass({
e.preventDefault();
this.props.onFinished(false);
}
},
};
_onCancelClick: function(e) {
_onCancelClick = (e) => {
this.props.onFinished(false);
},
};
render: function() {
render() {
let cancelButton;
if (this.props.hasCancel) {
cancelButton = (
@ -161,5 +157,5 @@ export default createReactClass({
</FocusLock>
</MatrixClientContext.Provider>
);
},
});
}
}

View file

@ -15,17 +15,14 @@ limitations under the License.
*/
import React from 'react';
import createReactClass from 'create-react-class';
import * as sdk from '../../../index';
import { _t } from '../../../languageHandler';
/*
* A dialog for confirming a redaction.
*/
export default createReactClass({
displayName: 'ConfirmRedactDialog',
render: function() {
export default class ConfirmRedactDialog extends React.Component {
render() {
const QuestionDialog = sdk.getComponent('views.dialogs.QuestionDialog');
return (
<QuestionDialog onFinished={this.props.onFinished}
@ -36,5 +33,5 @@ export default createReactClass({
button={_t("Remove")}>
</QuestionDialog>
);
},
});
}
}

View file

@ -15,7 +15,6 @@ limitations under the License.
*/
import React from 'react';
import createReactClass from 'create-react-class';
import PropTypes from 'prop-types';
import { MatrixClient } from 'matrix-js-sdk';
import * as sdk from '../../../index';
@ -30,9 +29,8 @@ import { GroupMemberType } from '../../../groups';
* to make it obvious what is going to happen.
* Also tweaks the style for 'dangerous' actions (albeit only with colour)
*/
export default createReactClass({
displayName: 'ConfirmUserActionDialog',
propTypes: {
export default class ConfirmUserActionDialog extends React.Component {
static propTypes = {
// matrix-js-sdk (room) member object. Supply either this or 'groupMember'
member: PropTypes.object,
// group member object. Supply either this or 'member'
@ -48,35 +46,36 @@ export default createReactClass({
askReason: PropTypes.bool,
danger: PropTypes.bool,
onFinished: PropTypes.func.isRequired,
},
};
getDefaultProps: () => ({
static defaultProps = {
danger: false,
askReason: false,
}),
};
constructor(props) {
super(props);
// TODO: [REACT-WARNING] Move this to constructor
UNSAFE_componentWillMount: function() {
this._reasonField = null;
},
}
onOk: function() {
onOk = () => {
let reason;
if (this._reasonField) {
reason = this._reasonField.value;
}
this.props.onFinished(true, reason);
},
};
onCancel: function() {
onCancel = () => {
this.props.onFinished(false);
},
};
_collectReasonField: function(e) {
_collectReasonField = e => {
this._reasonField = e;
},
};
render: function() {
render() {
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
const MemberAvatar = sdk.getComponent("views.avatars.MemberAvatar");
@ -134,5 +133,5 @@ export default createReactClass({
onCancel={this.onCancel} />
</BaseDialog>
);
},
});
}
}

View file

@ -15,46 +15,42 @@ limitations under the License.
*/
import React from 'react';
import createReactClass from 'create-react-class';
import PropTypes from 'prop-types';
import * as sdk from '../../../index';
import dis from '../../../dispatcher/dispatcher';
import { _t } from '../../../languageHandler';
import {MatrixClientPeg} from '../../../MatrixClientPeg';
export default createReactClass({
displayName: 'CreateGroupDialog',
propTypes: {
export default class CreateGroupDialog extends React.Component {
static propTypes = {
onFinished: PropTypes.func.isRequired,
},
};
getInitialState: function() {
return {
groupName: '',
groupId: '',
groupError: null,
creating: false,
createError: null,
};
},
state = {
groupName: '',
groupId: '',
groupError: null,
creating: false,
createError: null,
};
_onGroupNameChange: function(e) {
_onGroupNameChange = e => {
this.setState({
groupName: e.target.value,
});
},
};
_onGroupIdChange: function(e) {
_onGroupIdChange = e => {
this.setState({
groupId: e.target.value,
});
},
};
_onGroupIdBlur: function(e) {
_onGroupIdBlur = e => {
this._checkGroupId();
},
};
_checkGroupId: function(e) {
_checkGroupId(e) {
let error = null;
if (!this.state.groupId) {
error = _t("Community IDs cannot be empty.");
@ -67,9 +63,9 @@ export default createReactClass({
createError: null,
});
return error;
},
}
_onFormSubmit: function(e) {
_onFormSubmit = e => {
e.preventDefault();
if (this._checkGroupId()) return;
@ -94,13 +90,13 @@ export default createReactClass({
}).finally(() => {
this.setState({creating: false});
});
},
};
_onCancel: function() {
_onCancel = () => {
this.props.onFinished(false);
},
};
render: function() {
render() {
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
const Spinner = sdk.getComponent('elements.Spinner');
@ -171,5 +167,5 @@ export default createReactClass({
</form>
</BaseDialog>
);
},
});
}
}

View file

@ -16,7 +16,6 @@ limitations under the License.
*/
import React from 'react';
import createReactClass from 'create-react-class';
import PropTypes from 'prop-types';
import * as sdk from '../../../index';
import SdkConfig from '../../../SdkConfig';
@ -25,19 +24,19 @@ import { _t } from '../../../languageHandler';
import {MatrixClientPeg} from '../../../MatrixClientPeg';
import {Key} from "../../../Keyboard";
import {privateShouldBeEncrypted} from "../../../createRoom";
import TagOrderStore from "../../../stores/TagOrderStore";
import GroupStore from "../../../stores/GroupStore";
import {CommunityPrototypeStore} from "../../../stores/CommunityPrototypeStore";
export default createReactClass({
displayName: 'CreateRoomDialog',
propTypes: {
export default class CreateRoomDialog extends React.Component {
static propTypes = {
onFinished: PropTypes.func.isRequired,
defaultPublic: PropTypes.bool,
},
};
constructor(props) {
super(props);
getInitialState() {
const config = SdkConfig.get();
return {
this.state = {
isPublic: this.props.defaultPublic || false,
isEncrypted: privateShouldBeEncrypted(),
name: "",
@ -47,7 +46,7 @@ export default createReactClass({
noFederate: config.default_federate === false,
nameIsValid: false,
};
},
}
_roomCreateOptions() {
const opts = {};
@ -72,32 +71,32 @@ export default createReactClass({
opts.encryption = this.state.isEncrypted;
}
if (TagOrderStore.getSelectedPrototypeTag()) {
opts.associatedWithCommunity = TagOrderStore.getSelectedPrototypeTag();
if (CommunityPrototypeStore.instance.getSelectedCommunityId()) {
opts.associatedWithCommunity = CommunityPrototypeStore.instance.getSelectedCommunityId();
}
return opts;
},
}
componentDidMount() {
this._detailsRef.addEventListener("toggle", this.onDetailsToggled);
// move focus to first field when showing dialog
this._nameFieldRef.focus();
},
}
componentWillUnmount() {
this._detailsRef.removeEventListener("toggle", this.onDetailsToggled);
},
}
_onKeyDown: function(event) {
_onKeyDown = event => {
if (event.key === Key.ENTER) {
this.onOk();
event.preventDefault();
event.stopPropagation();
}
},
};
onOk: async function() {
onOk = async () => {
const activeElement = document.activeElement;
if (activeElement) {
activeElement.blur();
@ -123,51 +122,51 @@ export default createReactClass({
field.validate({ allowEmpty: false, focused: true });
}
}
},
};
onCancel: function() {
onCancel = () => {
this.props.onFinished(false);
},
};
onNameChange(ev) {
onNameChange = ev => {
this.setState({name: ev.target.value});
},
};
onTopicChange(ev) {
onTopicChange = ev => {
this.setState({topic: ev.target.value});
},
};
onPublicChange(isPublic) {
onPublicChange = isPublic => {
this.setState({isPublic});
},
};
onEncryptedChange(isEncrypted) {
onEncryptedChange = isEncrypted => {
this.setState({isEncrypted});
},
};
onAliasChange(alias) {
onAliasChange = alias => {
this.setState({alias});
},
};
onDetailsToggled(ev) {
onDetailsToggled = ev => {
this.setState({detailsOpen: ev.target.open});
},
};
onNoFederateChange(noFederate) {
onNoFederateChange = noFederate => {
this.setState({noFederate});
},
};
collectDetailsRef(ref) {
collectDetailsRef = ref => {
this._detailsRef = ref;
},
};
async onNameValidate(fieldState) {
const result = await this._validateRoomName(fieldState);
onNameValidate = async fieldState => {
const result = await CreateRoomDialog._validateRoomName(fieldState);
this.setState({nameIsValid: result.valid});
return result;
},
};
_validateRoomName: withValidation({
static _validateRoomName = withValidation({
rules: [
{
key: "required",
@ -175,9 +174,9 @@ export default createReactClass({
invalid: () => _t("Please enter a name for the room"),
},
],
}),
});
render: function() {
render() {
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
const Field = sdk.getComponent('views.elements.Field');
@ -198,7 +197,7 @@ export default createReactClass({
"Private rooms can be found and joined by invitation only. Public rooms can be " +
"found and joined by anyone.",
)}</p>;
if (TagOrderStore.getSelectedPrototypeTag()) {
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.",
@ -239,9 +238,8 @@ export default createReactClass({
}
let title = this.state.isPublic ? _t('Create a public room') : _t('Create a private room');
if (TagOrderStore.getSelectedPrototypeTag()) {
const summary = GroupStore.getSummary(TagOrderStore.getSelectedPrototypeTag());
const name = summary?.profile?.name || TagOrderStore.getSelectedPrototypeTag();
if (CommunityPrototypeStore.instance.getSelectedCommunityId()) {
const name = CommunityPrototypeStore.instance.getSelectedCommunityName();
title = _t("Create a room in %(communityName)s", {communityName: name});
}
return (
@ -275,5 +273,5 @@ export default createReactClass({
onCancel={this.onCancel} />
</BaseDialog>
);
},
});
}
}

View file

@ -0,0 +1,167 @@
/*
Copyright 2020 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import React, { ChangeEvent } from 'react';
import BaseDialog from "./BaseDialog";
import { _t } from "../../../languageHandler";
import { IDialogProps } from "./IDialogProps";
import Field from "../elements/Field";
import AccessibleButton from "../elements/AccessibleButton";
import { MatrixClientPeg } from "../../../MatrixClientPeg";
import { CommunityPrototypeStore } from "../../../stores/CommunityPrototypeStore";
import FlairStore from "../../../stores/FlairStore";
interface IProps extends IDialogProps {
communityId: string;
}
interface IState {
name: string;
error: string;
busy: boolean;
currentAvatarUrl: string;
avatarFile: File;
avatarPreview: string;
}
// XXX: This is a lot of duplication from the create dialog, just in a different shape
export default class EditCommunityPrototypeDialog extends React.PureComponent<IProps, IState> {
private avatarUploadRef: React.RefObject<HTMLInputElement> = React.createRef();
constructor(props: IProps) {
super(props);
const profile = CommunityPrototypeStore.instance.getCommunityProfile(props.communityId);
this.state = {
name: profile?.name || "",
error: null,
busy: false,
avatarFile: null,
avatarPreview: null,
currentAvatarUrl: profile?.avatarUrl,
};
}
private onNameChange = (ev: ChangeEvent<HTMLInputElement>) => {
this.setState({name: ev.target.value});
};
private onSubmit = async (ev) => {
ev.preventDefault();
ev.stopPropagation();
if (this.state.busy) return;
// We'll create the community now to see if it's taken, leaving it active in
// the background for the user to look at while they invite people.
this.setState({busy: true});
try {
let avatarUrl = this.state.currentAvatarUrl || ""; // must be a string for synapse to accept it
if (this.state.avatarFile) {
avatarUrl = await MatrixClientPeg.get().uploadContent(this.state.avatarFile);
}
await MatrixClientPeg.get().setGroupProfile(this.props.communityId, {
name: this.state.name,
avatar_url: avatarUrl,
});
// ask the flair store to update the profile too
await FlairStore.refreshGroupProfile(MatrixClientPeg.get(), this.props.communityId);
// we did it, so close the dialog
this.props.onFinished(true);
} catch (e) {
console.error(e);
this.setState({
busy: false,
error: _t("There was an error updating your community. The server is unable to process your request."),
});
}
};
private onAvatarChanged = (e: ChangeEvent<HTMLInputElement>) => {
if (!e.target.files || !e.target.files.length) {
this.setState({avatarFile: null});
} else {
this.setState({busy: true});
const file = e.target.files[0];
const reader = new FileReader();
reader.onload = (ev: ProgressEvent<FileReader>) => {
this.setState({avatarFile: file, busy: false, avatarPreview: ev.target.result as string});
};
reader.readAsDataURL(file);
}
};
private onChangeAvatar = () => {
if (this.avatarUploadRef.current) this.avatarUploadRef.current.click();
};
public render() {
let preview = <img src={this.state.avatarPreview} className="mx_EditCommunityPrototypeDialog_avatar" />;
if (!this.state.avatarPreview) {
if (this.state.currentAvatarUrl) {
const url = MatrixClientPeg.get().mxcUrlToHttp(this.state.currentAvatarUrl);
preview = <img src={url} className="mx_EditCommunityPrototypeDialog_avatar" />;
} else {
preview = <div className="mx_EditCommunityPrototypeDialog_placeholderAvatar" />
}
}
return (
<BaseDialog
className="mx_EditCommunityPrototypeDialog"
onFinished={this.props.onFinished}
title={_t("Update community")}
>
<form onSubmit={this.onSubmit}>
<div className="mx_Dialog_content">
<div className="mx_EditCommunityPrototypeDialog_rowName">
<Field
value={this.state.name}
onChange={this.onNameChange}
placeholder={_t("Enter name")}
label={_t("Enter name")}
/>
</div>
<div className="mx_EditCommunityPrototypeDialog_rowAvatar">
<input
type="file" style={{display: "none"}}
ref={this.avatarUploadRef} accept="image/*"
onChange={this.onAvatarChanged}
/>
<AccessibleButton
onClick={this.onChangeAvatar}
className="mx_EditCommunityPrototypeDialog_avatarContainer"
>{preview}</AccessibleButton>
<div className="mx_EditCommunityPrototypeDialog_tip">
<b>{_t("Add image (optional)")}</b>
<span>
{_t("An image will help people identify your community.")}
</span>
</div>
</div>
<AccessibleButton kind="primary" onClick={this.onSubmit} disabled={this.state.busy}>
{_t("Save")}
</AccessibleButton>
</div>
</form>
</BaseDialog>
);
}
}

View file

@ -26,14 +26,12 @@ limitations under the License.
*/
import React from 'react';
import createReactClass from 'create-react-class';
import PropTypes from 'prop-types';
import * as sdk from '../../../index';
import { _t } from '../../../languageHandler';
export default createReactClass({
displayName: 'ErrorDialog',
propTypes: {
export default class ErrorDialog extends React.Component {
static propTypes = {
title: PropTypes.string,
description: PropTypes.oneOfType([
PropTypes.element,
@ -43,18 +41,16 @@ export default createReactClass({
focus: PropTypes.bool,
onFinished: PropTypes.func.isRequired,
headerImage: PropTypes.string,
},
};
getDefaultProps: function() {
return {
focus: true,
title: null,
description: null,
button: null,
};
},
static defaultProps = {
focus: true,
title: null,
description: null,
button: null,
};
render: function() {
render() {
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
return (
<BaseDialog
@ -74,5 +70,5 @@ export default createReactClass({
</div>
</BaseDialog>
);
},
});
}
}

View file

@ -17,15 +17,13 @@ limitations under the License.
*/
import React from 'react';
import createReactClass from 'create-react-class';
import PropTypes from 'prop-types';
import * as sdk from '../../../index';
import { _t } from '../../../languageHandler';
import classNames from "classnames";
export default createReactClass({
displayName: 'InfoDialog',
propTypes: {
export default class InfoDialog extends React.Component {
static propTypes = {
className: PropTypes.string,
title: PropTypes.string,
description: PropTypes.node,
@ -33,21 +31,19 @@ export default createReactClass({
onFinished: PropTypes.func,
hasCloseButton: PropTypes.bool,
onKeyDown: PropTypes.func,
},
};
getDefaultProps: function() {
return {
title: '',
description: '',
hasCloseButton: false,
};
},
static defaultProps = {
title: '',
description: '',
hasCloseButton: false,
};
onFinished: function() {
onFinished = () => {
this.props.onFinished();
},
};
render: function() {
render() {
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
return (
@ -69,5 +65,5 @@ export default createReactClass({
</DialogButtons>
</BaseDialog>
);
},
});
}
}

View file

@ -17,7 +17,6 @@ limitations under the License.
*/
import React from 'react';
import createReactClass from 'create-react-class';
import PropTypes from 'prop-types';
import * as sdk from '../../../index';
@ -27,10 +26,8 @@ import AccessibleButton from '../elements/AccessibleButton';
import {ERROR_USER_CANCELLED} from "../../structures/InteractiveAuth";
import {SSOAuthEntry} from "../auth/InteractiveAuthEntryComponents";
export default createReactClass({
displayName: 'InteractiveAuthDialog',
propTypes: {
export default class InteractiveAuthDialog extends React.Component {
static propTypes = {
// matrix client to use for UI auth requests
matrixClient: PropTypes.object.isRequired,
@ -70,19 +67,17 @@ export default createReactClass({
//
// Default is defined in _getDefaultDialogAesthetics()
aestheticsForStagePhases: PropTypes.object,
},
};
getInitialState: function() {
return {
authError: null,
state = {
authError: null,
// See _onUpdateStagePhase()
uiaStage: null,
uiaStagePhase: null,
};
},
// See _onUpdateStagePhase()
uiaStage: null,
uiaStagePhase: null,
};
_getDefaultDialogAesthetics: function() {
_getDefaultDialogAesthetics() {
const ssoAesthetics = {
[SSOAuthEntry.PHASE_PREAUTH]: {
title: _t("Use Single Sign On to continue"),
@ -102,9 +97,9 @@ export default createReactClass({
[SSOAuthEntry.LOGIN_TYPE]: ssoAesthetics,
[SSOAuthEntry.UNSTABLE_LOGIN_TYPE]: ssoAesthetics,
};
},
}
_onAuthFinished: function(success, result) {
_onAuthFinished = (success, result) => {
if (success) {
this.props.onFinished(true, result);
} else {
@ -116,18 +111,18 @@ export default createReactClass({
});
}
}
},
};
_onUpdateStagePhase: function(newStage, newPhase) {
_onUpdateStagePhase = (newStage, newPhase) => {
// We copy the stage and stage phase params into state for title selection in render()
this.setState({uiaStage: newStage, uiaStagePhase: newPhase});
},
};
_onDismissClick: function() {
_onDismissClick = () => {
this.props.onFinished(false);
},
};
render: function() {
render() {
const InteractiveAuth = sdk.getComponent("structures.InteractiveAuth");
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
@ -190,5 +185,5 @@ export default createReactClass({
{ content }
</BaseDialog>
);
},
});
}
}

View file

@ -32,11 +32,12 @@ import IdentityAuthClient from "../../../IdentityAuthClient";
import Modal from "../../../Modal";
import {humanizeTime} from "../../../utils/humanize";
import createRoom, {canEncryptToAllUsers, privateShouldBeEncrypted} from "../../../createRoom";
import {inviteMultipleToRoom} from "../../../RoomInvite";
import {inviteMultipleToRoom, showCommunityInviteDialog} from "../../../RoomInvite";
import {Key} from "../../../Keyboard";
import {Action} from "../../../dispatcher/actions";
import {DefaultTagID} from "../../../stores/room-list/models";
import RoomListStore from "../../../stores/room-list/RoomListStore";
import {CommunityPrototypeStore} from "../../../stores/CommunityPrototypeStore";
// we have a number of types defined from the Matrix spec which can't reasonably be altered here.
/* eslint-disable camelcase */
@ -909,12 +910,23 @@ export default class InviteDialog extends React.PureComponent {
this.props.onFinished();
};
_onCommunityInviteClick = (e) => {
this.props.onFinished();
showCommunityInviteDialog(CommunityPrototypeStore.instance.getSelectedCommunityId());
};
_renderSection(kind: "recents"|"suggestions") {
let sourceMembers = kind === 'recents' ? this.state.recents : this.state.suggestions;
let showNum = kind === 'recents' ? this.state.numRecentsShown : this.state.numSuggestionsShown;
const showMoreFn = kind === 'recents' ? this._showMoreRecents.bind(this) : this._showMoreSuggestions.bind(this);
const lastActive = (m) => kind === 'recents' ? m.lastActive : null;
let sectionName = kind === 'recents' ? _t("Recent Conversations") : _t("Suggestions");
let sectionSubname = null;
if (kind === 'suggestions' && CommunityPrototypeStore.instance.getSelectedCommunityId()) {
const communityName = CommunityPrototypeStore.instance.getSelectedCommunityName();
sectionSubname = _t("May include members not in %(communityName)s", {communityName});
}
if (this.props.kind === KIND_INVITE) {
sectionName = kind === 'recents' ? _t("Recently Direct Messaged") : _t("Suggestions");
@ -993,6 +1005,7 @@ export default class InviteDialog extends React.PureComponent {
return (
<div className='mx_InviteDialog_section'>
<h3>{sectionName}</h3>
{sectionSubname ? <p className="mx_InviteDialog_subname">{sectionSubname}</p> : null}
{tiles}
{showMore}
</div>
@ -1083,6 +1096,33 @@ export default class InviteDialog extends React.PureComponent {
return <a href={makeUserPermalink(userId)} rel="noreferrer noopener" target="_blank">{userId}</a>;
}},
);
if (CommunityPrototypeStore.instance.getSelectedCommunityId()) {
const communityName = CommunityPrototypeStore.instance.getSelectedCommunityName();
helpText = _t(
"Start a conversation with someone using their name, username (like <userId/>) or email address. " +
"This won't invite them to %(communityName)s. To invite someone to %(communityName)s, click " +
"<a>here</a>.",
{communityName}, {
userId: () => {
return (
<a
href={makeUserPermalink(userId)}
rel="noreferrer noopener"
target="_blank"
>{userId}</a>
);
},
a: (sub) => {
return (
<AccessibleButton
kind="link"
onClick={this._onCommunityInviteClick}
>{sub}</AccessibleButton>
);
},
},
);
}
buttonText = _t("Go");
goButtonFn = this._startDm;
} else { // KIND_INVITE

View file

@ -16,14 +16,12 @@ limitations under the License.
*/
import React from 'react';
import createReactClass from 'create-react-class';
import PropTypes from 'prop-types';
import * as sdk from '../../../index';
import { _t } from '../../../languageHandler';
export default createReactClass({
displayName: 'QuestionDialog',
propTypes: {
export default class QuestionDialog extends React.Component {
static propTypes = {
title: PropTypes.string,
description: PropTypes.node,
extraButtons: PropTypes.node,
@ -34,29 +32,27 @@ export default createReactClass({
headerImage: PropTypes.string,
quitOnly: PropTypes.bool, // quitOnly doesn't show the cancel button just the quit [x].
fixedWidth: PropTypes.bool,
},
};
getDefaultProps: function() {
return {
title: "",
description: "",
extraButtons: null,
focus: true,
hasCancelButton: true,
danger: false,
quitOnly: false,
};
},
static defaultProps = {
title: "",
description: "",
extraButtons: null,
focus: true,
hasCancelButton: true,
danger: false,
quitOnly: false,
};
onOk: function() {
onOk = () => {
this.props.onFinished(true);
},
};
onCancel: function() {
onCancel = () => {
this.props.onFinished(false);
},
};
render: function() {
render() {
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
let primaryButtonClass = "";
@ -88,5 +84,5 @@ export default createReactClass({
</DialogButtons>
</BaseDialog>
);
},
});
}
}

View file

@ -15,38 +15,33 @@ limitations under the License.
*/
import React from 'react';
import createReactClass from 'create-react-class';
import PropTypes from 'prop-types';
import * as sdk from '../../../index';
import {MatrixClientPeg} from '../../../MatrixClientPeg';
import Modal from '../../../Modal';
import { _t } from '../../../languageHandler';
export default createReactClass({
displayName: 'RoomUpgradeDialog',
propTypes: {
export default class RoomUpgradeDialog extends React.Component {
static propTypes = {
room: PropTypes.object.isRequired,
onFinished: PropTypes.func.isRequired,
},
};
componentDidMount: async function() {
state = {
busy: true,
};
async componentDidMount() {
const recommended = await this.props.room.getRecommendedVersion();
this._targetVersion = recommended.version;
this.setState({busy: false});
},
}
getInitialState: function() {
return {
busy: true,
};
},
_onCancelClick: function() {
_onCancelClick = () => {
this.props.onFinished(false);
},
};
_onUpgradeClick: function() {
_onUpgradeClick = () => {
this.setState({busy: true});
MatrixClientPeg.get().upgradeRoom(this.props.room.roomId, this._targetVersion).then(() => {
this.props.onFinished(true);
@ -59,9 +54,9 @@ export default createReactClass({
}).finally(() => {
this.setState({busy: false});
});
},
};
render: function() {
render() {
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
const Spinner = sdk.getComponent('views.elements.Spinner');
@ -106,5 +101,5 @@ export default createReactClass({
{buttons}
</BaseDialog>
);
},
});
}
}

View file

@ -17,7 +17,6 @@ limitations under the License.
*/
import React from 'react';
import createReactClass from 'create-react-class';
import PropTypes from 'prop-types';
import * as sdk from '../../../index';
import SdkConfig from '../../../SdkConfig';
@ -25,20 +24,18 @@ import Modal from '../../../Modal';
import { _t } from '../../../languageHandler';
export default createReactClass({
displayName: 'SessionRestoreErrorDialog',
propTypes: {
export default class SessionRestoreErrorDialog extends React.Component {
static propTypes = {
error: PropTypes.string.isRequired,
onFinished: PropTypes.func.isRequired,
},
};
_sendBugReport: function() {
_sendBugReport = () => {
const BugReportDialog = sdk.getComponent("dialogs.BugReportDialog");
Modal.createTrackedDialog('Session Restore Error', 'Send Bug Report Dialog', BugReportDialog, {});
},
};
_onClearStorageClick: function() {
_onClearStorageClick = () => {
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
Modal.createTrackedDialog('Session Restore Confirm Logout', '', QuestionDialog, {
title: _t("Sign out"),
@ -48,15 +45,15 @@ export default createReactClass({
danger: true,
onFinished: this.props.onFinished,
});
},
};
_onRefreshClick: function() {
_onRefreshClick = () => {
// Is this likely to help? Probably not, but giving only one button
// that clears your storage seems awful.
window.location.reload(true);
},
};
render: function() {
render() {
const brand = SdkConfig.get().brand;
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
@ -110,5 +107,5 @@ export default createReactClass({
{ dialogButtons }
</BaseDialog>
);
},
});
}
}

View file

@ -16,7 +16,6 @@ limitations under the License.
*/
import React from 'react';
import createReactClass from 'create-react-class';
import PropTypes from 'prop-types';
import * as sdk from '../../../index';
import * as Email from '../../../email';
@ -25,31 +24,28 @@ import { _t } from '../../../languageHandler';
import Modal from '../../../Modal';
/**
/*
* Prompt the user to set an email address.
*
* On success, `onFinished(true)` is called.
*/
export default createReactClass({
displayName: 'SetEmailDialog',
propTypes: {
export default class SetEmailDialog extends React.Component {
static propTypes = {
onFinished: PropTypes.func.isRequired,
},
};
getInitialState: function() {
return {
emailAddress: '',
emailBusy: false,
};
},
state = {
emailAddress: '',
emailBusy: false,
};
onEmailAddressChanged: function(value) {
onEmailAddressChanged = value => {
this.setState({
emailAddress: value,
});
},
};
onSubmit: function() {
onSubmit = () => {
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
@ -81,21 +77,21 @@ export default createReactClass({
});
});
this.setState({emailBusy: true});
},
};
onCancelled: function() {
onCancelled = () => {
this.props.onFinished(false);
},
};
onEmailDialogFinished: function(ok) {
onEmailDialogFinished = ok => {
if (ok) {
this.verifyEmailAddress();
} else {
this.setState({emailBusy: false});
}
},
};
verifyEmailAddress: function() {
verifyEmailAddress() {
this._addThreepid.checkEmailLinkClicked().then(() => {
this.props.onFinished(true);
}, (err) => {
@ -119,9 +115,9 @@ export default createReactClass({
});
}
});
},
}
render: function() {
render() {
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
const Spinner = sdk.getComponent('elements.Spinner');
const EditableText = sdk.getComponent('elements.EditableText');
@ -161,5 +157,5 @@ export default createReactClass({
</div>
</BaseDialog>
);
},
});
}
}

View file

@ -16,7 +16,6 @@ limitations under the License.
*/
import React, {createRef} from 'react';
import createReactClass from 'create-react-class';
import PropTypes from 'prop-types';
import * as sdk from '../../../index';
import {MatrixClientPeg} from '../../../MatrixClientPeg';
@ -29,23 +28,27 @@ import { SAFE_LOCALPART_REGEX } from '../../../Registration';
// sending a request to the server
const USERNAME_CHECK_DEBOUNCE_MS = 250;
/**
/*
* Prompt the user to set a display name.
*
* On success, `onFinished(true, newDisplayName)` is called.
*/
export default createReactClass({
displayName: 'SetMxIdDialog',
propTypes: {
export default class SetMxIdDialog extends React.Component {
static propTypes = {
onFinished: PropTypes.func.isRequired,
// Called when the user requests to register with a different homeserver
onDifferentServerClicked: PropTypes.func.isRequired,
// Called if the user wants to switch to login instead
onLoginClick: PropTypes.func.isRequired,
},
};
getInitialState: function() {
return {
constructor(props) {
super(props);
this._input_value = createRef();
this._uiAuth = createRef();
this.state = {
// The entered username
username: '',
// Indicate ongoing work on the username
@ -60,21 +63,15 @@ export default createReactClass({
// Indicate error with auth
authError: '',
};
},
}
// TODO: [REACT-WARNING] Replace component with real class, use constructor for refs
UNSAFE_componentWillMount: function() {
this._input_value = createRef();
this._uiAuth = createRef();
},
componentDidMount: function() {
componentDidMount() {
this._input_value.current.select();
this._matrixClient = MatrixClientPeg.get();
},
}
onValueChange: function(ev) {
onValueChange = ev => {
this.setState({
username: ev.target.value,
usernameBusy: true,
@ -99,24 +96,24 @@ export default createReactClass({
});
}, USERNAME_CHECK_DEBOUNCE_MS);
});
},
};
onKeyUp: function(ev) {
onKeyUp = ev => {
if (ev.key === Key.ENTER) {
this.onSubmit();
}
},
};
onSubmit: function(ev) {
onSubmit = ev => {
if (this._uiAuth.current) {
this._uiAuth.current.tryContinue();
}
this.setState({
doingUIAuth: true,
});
},
};
_doUsernameCheck: function() {
_doUsernameCheck() {
// We do a quick check ahead of the username availability API to ensure the
// user ID roughly looks okay from a Matrix perspective.
if (!SAFE_LOCALPART_REGEX.test(this.state.username)) {
@ -167,13 +164,13 @@ export default createReactClass({
this.setState(newState);
},
);
},
}
_generatePassword: function() {
_generatePassword() {
return Math.random().toString(36).slice(2);
},
}
_makeRegisterRequest: function(auth) {
_makeRegisterRequest = auth => {
// Not upgrading - changing mxids
const guestAccessToken = null;
if (!this._generatedPassword) {
@ -187,9 +184,9 @@ export default createReactClass({
{},
guestAccessToken,
);
},
};
_onUIAuthFinished: function(success, response) {
_onUIAuthFinished = (success, response) => {
this.setState({
doingUIAuth: false,
});
@ -207,9 +204,9 @@ export default createReactClass({
accessToken: response.access_token,
password: this._generatedPassword,
});
},
};
render: function() {
render() {
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
const InteractiveAuth = sdk.getComponent('structures.InteractiveAuth');
@ -303,5 +300,5 @@ export default createReactClass({
</div>
</BaseDialog>
);
},
});
}
}

View file

@ -17,7 +17,6 @@ limitations under the License.
*/
import React from 'react';
import createReactClass from 'create-react-class';
import PropTypes from 'prop-types';
import * as sdk from '../../../index';
import { _t } from '../../../languageHandler';
@ -63,32 +62,25 @@ const WarmFuzzy = function(props) {
*
* On success, `onFinished()` when finished
*/
export default createReactClass({
displayName: 'SetPasswordDialog',
propTypes: {
export default class SetPasswordDialog extends React.Component {
static propTypes = {
onFinished: PropTypes.func.isRequired,
},
};
getInitialState: function() {
return {
error: null,
};
},
state = {
error: null,
};
componentDidMount: function() {
console.info('SetPasswordDialog component did mount');
},
_onPasswordChanged: function(res) {
_onPasswordChanged = res => {
Modal.createDialog(WarmFuzzy, {
didSetEmail: res.didSetEmail,
onFinished: () => {
this.props.onFinished();
},
});
},
};
_onPasswordChangeError: function(err) {
_onPasswordChangeError = err => {
let errMsg = err.error || "";
if (err.httpStatus === 403) {
errMsg = _t('Failed to change password. Is your password correct?');
@ -101,9 +93,9 @@ export default createReactClass({
this.setState({
error: errMsg,
});
},
};
render: function() {
render() {
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
const ChangePassword = sdk.getComponent('views.settings.ChangePassword');
@ -132,5 +124,5 @@ export default createReactClass({
</div>
</BaseDialog>
);
},
});
}
}

View file

@ -15,14 +15,12 @@ limitations under the License.
*/
import React, {createRef} from 'react';
import createReactClass from 'create-react-class';
import PropTypes from 'prop-types';
import * as sdk from '../../../index';
import Field from "../elements/Field";
export default createReactClass({
displayName: 'TextInputDialog',
propTypes: {
export default class TextInputDialog extends React.Component {
static propTypes = {
title: PropTypes.string,
description: PropTypes.oneOfType([
PropTypes.element,
@ -36,39 +34,36 @@ export default createReactClass({
hasCancel: PropTypes.bool,
validator: PropTypes.func, // result of withValidation
fixedWidth: PropTypes.bool,
},
};
getDefaultProps: function() {
return {
title: "",
value: "",
description: "",
focus: true,
hasCancel: true,
};
},
static defaultProps = {
title: "",
value: "",
description: "",
focus: true,
hasCancel: true,
};
getInitialState: function() {
return {
constructor(props) {
super(props);
this._field = createRef();
this.state = {
value: this.props.value,
valid: false,
};
},
}
// TODO: [REACT-WARNING] Replace component with real class, use constructor for refs
UNSAFE_componentWillMount: function() {
this._field = createRef();
},
componentDidMount: function() {
componentDidMount() {
if (this.props.focus) {
// Set the cursor at the end of the text input
// this._field.current.value = this.props.value;
this._field.current.focus();
}
},
}
onOk: async function(ev) {
onOk = async ev => {
ev.preventDefault();
if (this.props.validator) {
await this._field.current.validate({ allowEmpty: false });
@ -80,27 +75,27 @@ export default createReactClass({
}
}
this.props.onFinished(true, this.state.value);
},
};
onCancel: function() {
onCancel = () => {
this.props.onFinished(false);
},
};
onChange: function(ev) {
onChange = ev => {
this.setState({
value: ev.target.value,
});
},
};
onValidate: async function(fieldState) {
onValidate = async fieldState => {
const result = await this.props.validator(fieldState);
this.setState({
valid: result.valid,
});
return result;
},
};
render: function() {
render() {
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
return (
@ -137,5 +132,5 @@ export default createReactClass({
/>
</BaseDialog>
);
},
});
}
}

View file

@ -21,7 +21,7 @@ import * as sdk from '../../../../index';
import {MatrixClientPeg} from '../../../../MatrixClientPeg';
import { MatrixClient } from 'matrix-js-sdk';
import { _t } from '../../../../languageHandler';
import { accessSecretStorage } from '../../../../CrossSigningManager';
import { accessSecretStorage } from '../../../../SecurityManager';
const RESTORE_TYPE_PASSPHRASE = 0;
const RESTORE_TYPE_RECOVERYKEY = 1;

View file

@ -15,7 +15,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import { debounce } from 'lodash';
import {debounce} from "lodash";
import classNames from 'classnames';
import React from 'react';
import PropTypes from "prop-types";

View file

@ -16,16 +16,13 @@ limitations under the License.
import React from 'react';
import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import AccessibleButton from './AccessibleButton';
import dis from '../../../dispatcher/dispatcher';
import * as sdk from '../../../index';
import Analytics from '../../../Analytics';
export default createReactClass({
displayName: 'RoleButton',
propTypes: {
export default class ActionButton extends React.Component {
static propTypes = {
size: PropTypes.string,
tooltip: PropTypes.bool,
action: PropTypes.string.isRequired,
@ -33,39 +30,35 @@ export default createReactClass({
label: PropTypes.string.isRequired,
iconPath: PropTypes.string,
className: PropTypes.string,
},
};
getDefaultProps: function() {
return {
size: "25",
tooltip: false,
};
},
static defaultProps = {
size: "25",
tooltip: false,
};
getInitialState: function() {
return {
showTooltip: false,
};
},
state = {
showTooltip: false,
};
_onClick: function(ev) {
_onClick = (ev) => {
ev.stopPropagation();
Analytics.trackEvent('Action Button', 'click', this.props.action);
dis.dispatch({action: this.props.action});
},
};
_onMouseEnter: function() {
_onMouseEnter = () => {
if (this.props.tooltip) this.setState({showTooltip: true});
if (this.props.mouseOverAction) {
dis.dispatch({action: this.props.mouseOverAction});
}
},
};
_onMouseLeave: function() {
_onMouseLeave = () => {
this.setState({showTooltip: false});
},
};
render: function() {
render() {
const TintableSvg = sdk.getComponent("elements.TintableSvg");
let tooltip;
@ -94,5 +87,5 @@ export default createReactClass({
{ tooltip }
</AccessibleButton>
);
},
});
}
}

View file

@ -17,15 +17,12 @@ limitations under the License.
import React from 'react';
import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import * as sdk from '../../../index';
import classNames from 'classnames';
import { UserAddressType } from '../../../UserAddress';
export default createReactClass({
displayName: 'AddressSelector',
propTypes: {
export default class AddressSelector extends React.Component {
static propTypes = {
onSelected: PropTypes.func.isRequired,
// List of the addresses to display
@ -37,90 +34,91 @@ export default createReactClass({
// Element to put as a header on top of the list
header: PropTypes.node,
},
};
getInitialState: function() {
return {
constructor(props) {
super(props);
this.state = {
selected: this.props.selected === undefined ? 0 : this.props.selected,
hover: false,
};
},
}
// TODO: [REACT-WARNING] Replace with appropriate lifecycle event
UNSAFE_componentWillReceiveProps: function(props) {
UNSAFE_componentWillReceiveProps(props) {
// Make sure the selected item isn't outside the list bounds
const selected = this.state.selected;
const maxSelected = this._maxSelected(props.addressList);
if (selected > maxSelected) {
this.setState({ selected: maxSelected });
}
},
}
componentDidUpdate: function() {
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;
}
},
}
moveSelectionTop: function() {
moveSelectionTop = () => {
if (this.state.selected > 0) {
this.setState({
selected: 0,
hover: false,
});
}
},
};
moveSelectionUp: function() {
moveSelectionUp = () => {
if (this.state.selected > 0) {
this.setState({
selected: this.state.selected - 1,
hover: false,
});
}
},
};
moveSelectionDown: function() {
moveSelectionDown = () => {
if (this.state.selected < this._maxSelected(this.props.addressList)) {
this.setState({
selected: this.state.selected + 1,
hover: false,
});
}
},
};
chooseSelection: function() {
chooseSelection = () => {
this.selectAddress(this.state.selected);
},
};
onClick: function(index) {
onClick = index => {
this.selectAddress(index);
},
};
onMouseEnter: function(index) {
onMouseEnter = index => {
this.setState({
selected: index,
hover: true,
});
},
};
onMouseLeave: function() {
onMouseLeave = () => {
this.setState({ hover: false });
},
};
selectAddress: function(index) {
selectAddress = index => {
// Only try to select an address if one exists
if (this.props.addressList.length !== 0) {
this.props.onSelected(index);
this.setState({ hover: false });
}
},
};
createAddressListTiles: function() {
const self = this;
createAddressListTiles() {
const AddressTile = sdk.getComponent("elements.AddressTile");
const maxSelected = this._maxSelected(this.props.addressList);
const addressList = [];
@ -157,15 +155,15 @@ export default createReactClass({
}
}
return addressList;
},
}
_maxSelected: function(list) {
_maxSelected(list) {
const listSize = list.length === 0 ? 0 : list.length - 1;
const maxSelected = listSize > (this.props.truncateAt - 1) ? (this.props.truncateAt - 1) : listSize;
return maxSelected;
},
}
render: function() {
render() {
const classes = classNames({
"mx_AddressSelector": true,
"mx_AddressSelector_empty": this.props.addressList.length === 0,
@ -177,5 +175,5 @@ export default createReactClass({
{ this.createAddressListTiles() }
</div>
);
},
});
}
}

View file

@ -17,7 +17,6 @@ limitations under the License.
import React from 'react';
import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import classNames from 'classnames';
import * as sdk from "../../../index";
import {MatrixClientPeg} from "../../../MatrixClientPeg";
@ -25,25 +24,21 @@ import { _t } from '../../../languageHandler';
import { UserAddressType } from '../../../UserAddress.js';
export default createReactClass({
displayName: 'AddressTile',
propTypes: {
export default class AddressTile extends React.Component {
static propTypes = {
address: UserAddressType.isRequired,
canDismiss: PropTypes.bool,
onDismissed: PropTypes.func,
justified: PropTypes.bool,
},
};
getDefaultProps: function() {
return {
canDismiss: false,
onDismissed: function() {}, // NOP
justified: false,
};
},
static defaultProps = {
canDismiss: false,
onDismissed: function() {}, // NOP
justified: false,
};
render: function() {
render() {
const address = this.props.address;
const name = address.displayName || address.address;
@ -144,5 +139,5 @@ export default createReactClass({
{ dismiss }
</div>
);
},
});
}
}

View file

@ -18,16 +18,13 @@ limitations under the License.
import React from "react";
import PropTypes from "prop-types";
import createReactClass from 'create-react-class';
import { _t } from '../../../languageHandler';
/**
* Basic container for buttons in modal dialogs.
*/
export default createReactClass({
displayName: "DialogButtons",
propTypes: {
export default class DialogButtons extends React.Component {
static propTypes = {
// The primary button which is styled differently and has default focus.
primaryButton: PropTypes.node.isRequired,
@ -57,20 +54,18 @@ export default createReactClass({
// disables only the primary button
primaryDisabled: PropTypes.bool,
},
};
getDefaultProps: function() {
return {
hasCancel: true,
disabled: false,
};
},
static defaultProps = {
hasCancel: true,
disabled: false,
};
_onCancelClick: function() {
_onCancelClick = () => {
this.props.onCancel();
},
};
render: function() {
render() {
let primaryButtonClassName = "mx_Dialog_primary";
if (this.props.primaryButtonClass) {
primaryButtonClassName += " " + this.props.primaryButtonClass;
@ -104,5 +99,5 @@ export default createReactClass({
</button>
</div>
);
},
});
}
}

View file

@ -17,13 +17,10 @@ limitations under the License.
import React, {createRef} from 'react';
import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import {Key} from "../../../Keyboard";
export default createReactClass({
displayName: 'EditableText',
propTypes: {
export default class EditableText extends React.Component {
static propTypes = {
onValueChanged: PropTypes.func,
initialValue: PropTypes.string,
label: PropTypes.string,
@ -36,60 +33,58 @@ export default createReactClass({
// Will cause onValueChanged(value, true) to fire on blur
blurToSubmit: PropTypes.bool,
editable: PropTypes.bool,
},
};
Phases: {
static Phases = {
Display: "display",
Edit: "edit",
},
};
getDefaultProps: function() {
return {
onValueChanged: function() {},
initialValue: '',
label: '',
placeholder: '',
editable: true,
className: "mx_EditableText",
placeholderClassName: "mx_EditableText_placeholder",
blurToSubmit: false,
};
},
static defaultProps = {
onValueChanged() {},
initialValue: '',
label: '',
placeholder: '',
editable: true,
className: "mx_EditableText",
placeholderClassName: "mx_EditableText_placeholder",
blurToSubmit: false,
};
getInitialState: function() {
return {
phase: this.Phases.Display,
};
},
constructor(props) {
super(props);
// TODO: [REACT-WARNING] Replace with appropriate lifecycle event
UNSAFE_componentWillReceiveProps: function(nextProps) {
if (nextProps.initialValue !== this.props.initialValue) {
this.value = nextProps.initialValue;
if (this._editable_div.current) {
this.showPlaceholder(!this.value);
}
}
},
// TODO: [REACT-WARNING] Replace component with real class, use constructor for refs
UNSAFE_componentWillMount: function() {
// we track value as an JS object field rather than in React state
// as React doesn't play nice with contentEditable.
this.value = '';
this.placeholder = false;
this._editable_div = createRef();
},
}
componentDidMount: function() {
state = {
phase: EditableText.Phases.Display,
};
// TODO: [REACT-WARNING] Replace with appropriate lifecycle event
// eslint-disable-next-line camelcase
UNSAFE_componentWillReceiveProps(nextProps) {
if (nextProps.initialValue !== this.props.initialValue) {
this.value = nextProps.initialValue;
if (this._editable_div.current) {
this.showPlaceholder(!this.value);
}
}
}
componentDidMount() {
this.value = this.props.initialValue;
if (this._editable_div.current) {
this.showPlaceholder(!this.value);
}
},
}
showPlaceholder: function(show) {
showPlaceholder = show => {
if (show) {
this._editable_div.current.textContent = this.props.placeholder;
this._editable_div.current.setAttribute("class", this.props.className
@ -101,38 +96,36 @@ export default createReactClass({
this._editable_div.current.setAttribute("class", this.props.className);
this.placeholder = false;
}
},
};
getValue: function() {
return this.value;
},
getValue = () => this.value;
setValue: function(value) {
setValue = value => {
this.value = value;
this.showPlaceholder(!this.value);
},
};
edit: function() {
edit = () => {
this.setState({
phase: this.Phases.Edit,
phase: EditableText.Phases.Edit,
});
},
};
cancelEdit: function() {
cancelEdit = () => {
this.setState({
phase: this.Phases.Display,
phase: EditableText.Phases.Display,
});
this.value = this.props.initialValue;
this.showPlaceholder(!this.value);
this.onValueChanged(false);
this._editable_div.current.blur();
},
};
onValueChanged: function(shouldSubmit) {
onValueChanged = shouldSubmit => {
this.props.onValueChanged(this.value, shouldSubmit);
},
};
onKeyDown: function(ev) {
onKeyDown = ev => {
// console.log("keyDown: textContent=" + ev.target.textContent + ", value=" + this.value + ", placeholder=" + this.placeholder);
if (this.placeholder) {
@ -145,9 +138,9 @@ export default createReactClass({
}
// console.log("keyDown: textContent=" + ev.target.textContent + ", value=" + this.value + ", placeholder=" + this.placeholder);
},
};
onKeyUp: function(ev) {
onKeyUp = ev => {
// console.log("keyUp: textContent=" + ev.target.textContent + ", value=" + this.value + ", placeholder=" + this.placeholder);
if (!ev.target.textContent) {
@ -163,17 +156,17 @@ export default createReactClass({
}
// console.log("keyUp: textContent=" + ev.target.textContent + ", value=" + this.value + ", placeholder=" + this.placeholder);
},
};
onClickDiv: function(ev) {
onClickDiv = ev => {
if (!this.props.editable) return;
this.setState({
phase: this.Phases.Edit,
phase: EditableText.Phases.Edit,
});
},
};
onFocus: function(ev) {
onFocus = ev => {
//ev.target.setSelectionRange(0, ev.target.textContent.length);
const node = ev.target.childNodes[0];
@ -186,21 +179,21 @@ export default createReactClass({
sel.removeAllRanges();
sel.addRange(range);
}
},
};
onFinish: function(ev, shouldSubmit) {
onFinish = (ev, shouldSubmit) => {
const self = this;
const submit = (ev.key === Key.ENTER) || shouldSubmit;
this.setState({
phase: this.Phases.Display,
phase: EditableText.Phases.Display,
}, () => {
if (this.value !== this.props.initialValue) {
self.onValueChanged(submit);
}
});
},
};
onBlur: function(ev) {
onBlur = ev => {
const sel = window.getSelection();
sel.removeAllRanges();
@ -211,13 +204,15 @@ export default createReactClass({
}
this.showPlaceholder(!this.value);
},
};
render: function() {
render() {
const {className, editable, initialValue, label, labelClassName} = this.props;
let editableEl;
if (!editable || (this.state.phase === this.Phases.Display && (label || labelClassName) && !this.value)) {
if (!editable || (this.state.phase === EditableText.Phases.Display &&
(label || labelClassName) && !this.value)
) {
// show the label
editableEl = <div className={className + " " + labelClassName} onClick={this.onClickDiv}>
{ label || initialValue }
@ -234,5 +229,5 @@ export default createReactClass({
}
return editableEl;
},
});
}
}

View file

@ -17,7 +17,7 @@ limitations under the License.
import React, {InputHTMLAttributes, SelectHTMLAttributes, TextareaHTMLAttributes} from 'react';
import classNames from 'classnames';
import * as sdk from '../../../index';
import { debounce } from 'lodash';
import {debounce} from "lodash";
import {IFieldState, IValidationResult} from "./Validation";
// Invoke validation from user input (when typing, etc.) at most once every N ms.

View file

@ -15,14 +15,11 @@ limitations under the License.
*/
import React from "react";
import createReactClass from 'create-react-class';
import {_t} from "../../../languageHandler";
import SettingsStore from "../../../settings/SettingsStore";
export default createReactClass({
displayName: 'InlineSpinner',
render: function() {
export default class InlineSpinner extends React.Component {
render() {
const w = this.props.w || 16;
const h = this.props.h || 16;
const imgClass = this.props.imgClassName || "";
@ -45,5 +42,5 @@ export default createReactClass({
/>
</div>
);
},
});
}
}

View file

@ -18,17 +18,14 @@ limitations under the License.
import React from 'react';
import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import { _t } from '../../../languageHandler';
import { formatCommaSeparatedList } from '../../../utils/FormattingUtils';
import * as sdk from "../../../index";
import {MatrixEvent} from "matrix-js-sdk";
import {isValid3pidInvite} from "../../../RoomInvite";
export default createReactClass({
displayName: 'MemberEventListSummary',
propTypes: {
export default class MemberEventListSummary extends React.Component {
static propTypes = {
// An array of member events to summarise
events: PropTypes.arrayOf(PropTypes.instanceOf(MatrixEvent)).isRequired,
// An array of EventTiles to render when expanded
@ -43,17 +40,15 @@ export default createReactClass({
onToggle: PropTypes.func,
// Whether or not to begin with state.expanded=true
startExpanded: PropTypes.bool,
},
};
getDefaultProps: function() {
return {
summaryLength: 1,
threshold: 3,
avatarsMaxLength: 5,
};
},
static defaultProps = {
summaryLength: 1,
threshold: 3,
avatarsMaxLength: 5,
};
shouldComponentUpdate: function(nextProps) {
shouldComponentUpdate(nextProps) {
// Update if
// - The number of summarised events has changed
// - or if the summary is about to toggle to become collapsed
@ -62,7 +57,7 @@ export default createReactClass({
nextProps.events.length !== this.props.events.length ||
nextProps.events.length < this.props.threshold
);
},
}
/**
* Generate the text for users aggregated by their transition sequences (`eventAggregates`) where
@ -73,7 +68,7 @@ export default createReactClass({
* `Object.keys(eventAggregates)`.
* @returns {string} the textual summary of the aggregated events that occurred.
*/
_generateSummary: function(eventAggregates, orderedTransitionSequences) {
_generateSummary(eventAggregates, orderedTransitionSequences) {
const summaries = orderedTransitionSequences.map((transitions) => {
const userNames = eventAggregates[transitions];
const nameList = this._renderNameList(userNames);
@ -105,7 +100,7 @@ export default createReactClass({
}
return summaries.join(", ");
},
}
/**
* @param {string[]} users an array of user display names or user IDs.
@ -113,9 +108,9 @@ export default createReactClass({
* more items in `users` than `this.props.summaryLength`, which is the number of names
* included before "and [n] others".
*/
_renderNameList: function(users) {
_renderNameList(users) {
return formatCommaSeparatedList(users, this.props.summaryLength);
},
}
/**
* Canonicalise an array of transitions such that some pairs of transitions become
@ -124,7 +119,7 @@ export default createReactClass({
* @param {string[]} transitions an array of transitions.
* @returns {string[]} an array of transitions.
*/
_getCanonicalTransitions: function(transitions) {
_getCanonicalTransitions(transitions) {
const modMap = {
'joined': {
'after': 'left',
@ -155,7 +150,7 @@ export default createReactClass({
res.push(transition);
}
return res;
},
}
/**
* Transform an array of transitions into an array of transitions and how many times
@ -171,7 +166,7 @@ export default createReactClass({
* @param {string[]} transitions the array of transitions to transform.
* @returns {object[]} an array of coalesced transitions.
*/
_coalesceRepeatedTransitions: function(transitions) {
_coalesceRepeatedTransitions(transitions) {
const res = [];
for (let i = 0; i < transitions.length; i++) {
if (res.length > 0 && res[res.length - 1].transitionType === transitions[i]) {
@ -184,7 +179,7 @@ export default createReactClass({
}
}
return res;
},
}
/**
* For a certain transition, t, describe what happened to the users that
@ -268,11 +263,11 @@ export default createReactClass({
}
return res;
},
}
_getTransitionSequence: function(events) {
_getTransitionSequence(events) {
return events.map(this._getTransition);
},
}
/**
* Label a given membership event, `e`, where `getContent().membership` has
@ -282,7 +277,7 @@ export default createReactClass({
* @returns {string?} the transition type given to this event. This defaults to `null`
* if a transition is not recognised.
*/
_getTransition: function(e) {
_getTransition(e) {
if (e.mxEvent.getType() === 'm.room.third_party_invite') {
// Handle 3pid invites the same as invites so they get bundled together
if (!isValid3pidInvite(e.mxEvent)) {
@ -323,9 +318,9 @@ export default createReactClass({
}
default: return null;
}
},
}
_getAggregate: function(userEvents) {
_getAggregate(userEvents) {
// A map of aggregate type to arrays of display names. Each aggregate type
// is a comma-delimited string of transitions, e.g. "joined,left,kicked".
// The array of display names is the array of users who went through that
@ -364,9 +359,9 @@ export default createReactClass({
names: aggregate,
indices: aggregateIndices,
};
},
}
render: function() {
render() {
const eventsToRender = this.props.events;
// Map user IDs to an array of objects:
@ -420,5 +415,5 @@ export default createReactClass({
children={this.props.children}
summaryMembers={avatarMembers}
summaryText={this._generateSummary(aggregate.names, orderedTransitionSequences)} />;
},
});
}
}

View file

@ -16,49 +16,44 @@ limitations under the License.
*/
import React from 'react';
import createReactClass from 'create-react-class';
import RoomViewStore from '../../../stores/RoomViewStore';
import ActiveWidgetStore from '../../../stores/ActiveWidgetStore';
import WidgetUtils from '../../../utils/WidgetUtils';
import * as sdk from '../../../index';
import {MatrixClientPeg} from '../../../MatrixClientPeg';
export default createReactClass({
displayName: 'PersistentApp',
export default class PersistentApp extends React.Component {
state = {
roomId: RoomViewStore.getRoomId(),
persistentWidgetId: ActiveWidgetStore.getPersistentWidgetId(),
};
getInitialState: function() {
return {
roomId: RoomViewStore.getRoomId(),
persistentWidgetId: ActiveWidgetStore.getPersistentWidgetId(),
};
},
componentDidMount: function() {
componentDidMount() {
this._roomStoreToken = RoomViewStore.addListener(this._onRoomViewStoreUpdate);
ActiveWidgetStore.on('update', this._onActiveWidgetStoreUpdate);
},
}
componentWillUnmount: function() {
componentWillUnmount() {
if (this._roomStoreToken) {
this._roomStoreToken.remove();
}
ActiveWidgetStore.removeListener('update', this._onActiveWidgetStoreUpdate);
},
}
_onRoomViewStoreUpdate: function(payload) {
_onRoomViewStoreUpdate = payload => {
if (RoomViewStore.getRoomId() === this.state.roomId) return;
this.setState({
roomId: RoomViewStore.getRoomId(),
});
},
};
_onActiveWidgetStoreUpdate: function() {
_onActiveWidgetStoreUpdate = () => {
this.setState({
persistentWidgetId: ActiveWidgetStore.getPersistentWidgetId(),
});
},
};
render: function() {
render() {
if (this.state.persistentWidgetId) {
const persistentWidgetInRoomId = ActiveWidgetStore.getRoomId(this.state.persistentWidgetId);
if (this.state.roomId !== persistentWidgetInRoomId) {
@ -91,6 +86,6 @@ export default createReactClass({
}
}
return null;
},
});
}
}

View file

@ -16,7 +16,6 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import createReactClass from 'create-react-class';
import * as sdk from '../../../index';
import dis from '../../../dispatcher/dispatcher';
import classNames from 'classnames';
@ -32,27 +31,29 @@ import {Action} from "../../../dispatcher/actions";
// HttpUtils transformTags to relative links. This excludes event URLs (with `[^\/]*`)
const REGEX_LOCAL_PERMALINK = /^#\/(?:user|room|group)\/(([#!@+])[^/]*)$/;
const Pill = createReactClass({
statics: {
isPillUrl: (url) => {
return !!getPrimaryPermalinkEntity(url);
},
isMessagePillUrl: (url) => {
return !!REGEX_LOCAL_PERMALINK.exec(url);
},
roomNotifPos: (text) => {
return text.indexOf("@room");
},
roomNotifLen: () => {
return "@room".length;
},
TYPE_USER_MENTION: 'TYPE_USER_MENTION',
TYPE_ROOM_MENTION: 'TYPE_ROOM_MENTION',
TYPE_GROUP_MENTION: 'TYPE_GROUP_MENTION',
TYPE_AT_ROOM_MENTION: 'TYPE_AT_ROOM_MENTION', // '@room' mention
},
class Pill extends React.Component {
static isPillUrl(url) {
return !!getPrimaryPermalinkEntity(url);
}
props: {
static isMessagePillUrl(url) {
return !!REGEX_LOCAL_PERMALINK.exec(url);
}
static roomNotifPos(text) {
return text.indexOf("@room");
}
static roomNotifLen() {
return "@room".length;
}
static TYPE_USER_MENTION = 'TYPE_USER_MENTION';
static TYPE_ROOM_MENTION = 'TYPE_ROOM_MENTION';
static TYPE_GROUP_MENTION = 'TYPE_GROUP_MENTION';
static TYPE_AT_ROOM_MENTION = 'TYPE_AT_ROOM_MENTION'; // '@room' mention
static propTypes = {
// The Type of this Pill. If url is given, this is auto-detected.
type: PropTypes.string,
// The URL to pillify (no validation is done, see isPillUrl and isMessagePillUrl)
@ -65,25 +66,24 @@ const Pill = createReactClass({
shouldShowPillAvatar: PropTypes.bool,
// Whether to render this pill as if it were highlit by a selection
isSelected: PropTypes.bool,
},
};
getInitialState() {
return {
// ID/alias of the room/user
resourceId: null,
// Type of pill
pillType: null,
state = {
// ID/alias of the room/user
resourceId: null,
// Type of pill
pillType: null,
// The member related to the user pill
member: null,
// The group related to the group pill
group: null,
// The room related to the room pill
room: null,
};
},
// The member related to the user pill
member: null,
// The group related to the group pill
group: null,
// The room related to the room pill
room: null,
};
// TODO: [REACT-WARNING] Replace with appropriate lifecycle event
// eslint-disable-next-line camelcase
async UNSAFE_componentWillReceiveProps(nextProps) {
let resourceId;
let prefix;
@ -155,7 +155,7 @@ const Pill = createReactClass({
}
}
this.setState({resourceId, pillType, member, group, room});
},
}
componentDidMount() {
this._unmounted = false;
@ -163,13 +163,13 @@ const Pill = createReactClass({
// eslint-disable-next-line new-cap
this.UNSAFE_componentWillReceiveProps(this.props); // HACK: We shouldn't be calling lifecycle functions ourselves.
},
}
componentWillUnmount() {
this._unmounted = true;
},
}
doProfileLookup: function(userId, member) {
doProfileLookup(userId, member) {
MatrixClientPeg.get().getProfileInfo(userId).then((resp) => {
if (this._unmounted) {
return;
@ -188,15 +188,16 @@ const Pill = createReactClass({
}).catch((err) => {
console.error('Could not retrieve profile data for ' + userId + ':', err);
});
},
}
onUserPillClicked: function() {
onUserPillClicked = () => {
dis.dispatch({
action: Action.ViewUser,
member: this.state.member,
});
},
render: function() {
};
render() {
const BaseAvatar = sdk.getComponent('views.avatars.BaseAvatar');
const MemberAvatar = sdk.getComponent('avatars.MemberAvatar');
const RoomAvatar = sdk.getComponent('avatars.RoomAvatar');
@ -285,7 +286,7 @@ const Pill = createReactClass({
// Deliberately render nothing if the URL isn't recognised
return null;
}
},
});
}
}
export default Pill;

View file

@ -16,16 +16,13 @@ limitations under the License.
import React from 'react';
import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import * as Roles from '../../../Roles';
import { _t } from '../../../languageHandler';
import Field from "./Field";
import {Key} from "../../../Keyboard";
export default createReactClass({
displayName: 'PowerSelector',
propTypes: {
export default class PowerSelector extends React.Component {
static propTypes = {
value: PropTypes.number.isRequired,
// The maximum value that can be set with the power selector
maxValue: PropTypes.number.isRequired,
@ -42,10 +39,17 @@ export default createReactClass({
// The name to annotate the selector with
label: PropTypes.string,
},
}
getInitialState: function() {
return {
static defaultProps = {
maxValue: Infinity,
usersDefault: 0,
};
constructor(props) {
super(props);
this.state = {
levelRoleMap: {},
// List of power levels to show in the drop-down
options: [],
@ -53,26 +57,17 @@ export default createReactClass({
customValue: this.props.value,
selectValue: 0,
};
},
getDefaultProps: function() {
return {
maxValue: Infinity,
usersDefault: 0,
};
},
componentDidMount: function() {
// TODO: [REACT-WARNING] Move this to class constructor
this._initStateFromProps(this.props);
},
}
// TODO: [REACT-WARNING] Replace with appropriate lifecycle event
UNSAFE_componentWillReceiveProps: function(newProps) {
// eslint-disable-next-line camelcase
UNSAFE_componentWillReceiveProps(newProps) {
this._initStateFromProps(newProps);
},
}
_initStateFromProps: function(newProps) {
_initStateFromProps(newProps) {
// This needs to be done now because levelRoleMap has translated strings
const levelRoleMap = Roles.levelRoleMap(newProps.usersDefault);
const options = Object.keys(levelRoleMap).filter(level => {
@ -92,9 +87,9 @@ export default createReactClass({
customLevel: newProps.value,
selectValue: isCustom ? "SELECT_VALUE_CUSTOM" : newProps.value,
});
},
}
onSelectChange: function(event) {
onSelectChange = event => {
const isCustom = event.target.value === "SELECT_VALUE_CUSTOM";
if (isCustom) {
this.setState({custom: true});
@ -102,20 +97,20 @@ export default createReactClass({
this.props.onChange(event.target.value, this.props.powerLevelKey);
this.setState({selectValue: event.target.value});
}
},
};
onCustomChange: function(event) {
onCustomChange = event => {
this.setState({customValue: event.target.value});
},
};
onCustomBlur: function(event) {
onCustomBlur = event => {
event.preventDefault();
event.stopPropagation();
this.props.onChange(parseInt(this.state.customValue), this.props.powerLevelKey);
},
};
onCustomKeyDown: function(event) {
onCustomKeyDown = event => {
if (event.key === Key.ENTER) {
event.preventDefault();
event.stopPropagation();
@ -127,9 +122,9 @@ export default createReactClass({
// handle the onBlur safely.
event.target.blur();
}
},
};
render: function() {
render() {
let picker;
const label = typeof this.props.label === "undefined" ? _t("Power level") : this.props.label;
if (this.state.custom) {
@ -166,5 +161,5 @@ export default createReactClass({
{ picker }
</div>
);
},
});
}
}

View file

@ -45,8 +45,8 @@ export default class ReplyThread extends React.Component {
static contextType = MatrixClientContext;
constructor(props) {
super(props);
constructor(props, context) {
super(props, context);
this.state = {
// The loaded events to be rendered as linear-replies
@ -331,8 +331,14 @@ export default class ReplyThread extends React.Component {
{
_t('<a>In reply to</a> <pill>', {}, {
'a': (sub) => <a onClick={this.onQuoteClick} className="mx_ReplyThread_show">{ sub }</a>,
'pill': <Pill type={Pill.TYPE_USER_MENTION} room={room}
url={makeUserPermalink(ev.getSender())} shouldShowPillAvatar={true} />,
'pill': (
<Pill
type={Pill.TYPE_USER_MENTION}
room={room}
url={makeUserPermalink(ev.getSender())}
shouldShowPillAvatar={SettingsStore.getValue("Pill.shouldShowPillAvatar")}
/>
),
})
}
</blockquote>;

View file

@ -18,7 +18,6 @@ limitations under the License.
import React from 'react';
import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import classNames from 'classnames';
import * as sdk from '../../../index';
import dis from '../../../dispatcher/dispatcher';
@ -37,10 +36,8 @@ import SettingsStore from "../../../settings/SettingsStore";
// - Rooms that are part of the group
// - Direct messages with members of the group
// with the intention that this could be expanded to arbitrary tags in future.
export default createReactClass({
displayName: 'TagTile',
propTypes: {
export default class TagTile extends React.Component {
static propTypes = {
// A string tag such as "m.favourite" or a group ID such as "+groupid:domain.bla"
// For now, only group IDs are handled.
tag: PropTypes.string,
@ -48,20 +45,16 @@ export default createReactClass({
openMenu: PropTypes.func,
menuDisplayed: PropTypes.bool,
selected: PropTypes.bool,
},
};
statics: {
contextType: MatrixClientContext,
},
static contextType = MatrixClientContext;
getInitialState() {
return {
// Whether the mouse is over the tile
hover: false,
// The profile data of the group if this.props.tag is a group ID
profile: null,
};
},
state = {
// Whether the mouse is over the tile
hover: false,
// The profile data of the group if this.props.tag is a group ID
profile: null,
};
componentDidMount() {
this.unmounted = false;
@ -71,16 +64,16 @@ export default createReactClass({
// New rooms or members may have been added to the group, fetch async
this._refreshGroup(this.props.tag);
}
},
}
componentWillUnmount() {
this.unmounted = true;
if (this.props.tag[0] === '+') {
FlairStore.removeListener('updateGroupProfile', this._onFlairStoreUpdated);
}
},
}
_onFlairStoreUpdated() {
_onFlairStoreUpdated = () => {
if (this.unmounted) return;
FlairStore.getGroupProfileCached(
this.context,
@ -91,14 +84,14 @@ export default createReactClass({
}).catch((err) => {
console.warn('Could not fetch group profile for ' + this.props.tag, err);
});
},
};
_refreshGroup(groupId) {
GroupStore.refreshGroupRooms(groupId);
GroupStore.refreshGroupMembers(groupId);
},
}
onClick: function(e) {
onClick = e => {
e.preventDefault();
e.stopPropagation();
dis.dispatch({
@ -111,27 +104,27 @@ export default createReactClass({
// New rooms or members may have been added to the group, fetch async
this._refreshGroup(this.props.tag);
}
},
};
onMouseOver: function() {
onMouseOver = () => {
if (SettingsStore.getValue("feature_communities_v2_prototypes")) return;
this.setState({ hover: true });
},
};
onMouseLeave: function() {
onMouseLeave = () => {
this.setState({ hover: false });
},
};
openMenu: function(e) {
openMenu = e => {
// Prevent the TagTile onClick event firing as well
e.stopPropagation();
e.preventDefault();
if (SettingsStore.getValue("feature_communities_v2_prototypes")) return;
this.setState({ hover: false });
this.props.openMenu();
},
};
render: function() {
render() {
const BaseAvatar = sdk.getComponent('avatars.BaseAvatar');
const profile = this.state.profile || {};
const name = profile.name || this.props.tag;
@ -192,5 +185,5 @@ export default createReactClass({
{badgeElement}
</div>
</AccessibleTooltipButton>;
},
});
}
}

View file

@ -17,49 +17,44 @@ limitations under the License.
import React from 'react';
import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import Tinter from "../../../Tinter";
const TintableSvg = createReactClass({
displayName: 'TintableSvg',
propTypes: {
class TintableSvg extends React.Component {
static propTypes = {
src: PropTypes.string.isRequired,
width: PropTypes.string.isRequired,
height: PropTypes.string.isRequired,
className: PropTypes.string,
},
};
statics: {
// list of currently mounted TintableSvgs
mounts: {},
idSequence: 0,
},
// list of currently mounted TintableSvgs
static mounts = {};
static idSequence = 0;
componentDidMount: function() {
componentDidMount() {
this.fixups = [];
this.id = TintableSvg.idSequence++;
TintableSvg.mounts[this.id] = this;
},
}
componentWillUnmount: function() {
componentWillUnmount() {
delete TintableSvg.mounts[this.id];
},
}
tint: function() {
tint = () => {
// TODO: only bother running this if the global tint settings have changed
// since we loaded!
Tinter.applySvgFixups(this.fixups);
},
};
onLoad: function(event) {
onLoad = event => {
// console.log("TintableSvg.onLoad for " + this.props.src);
this.fixups = Tinter.calcSvgFixups([event.target]);
Tinter.applySvgFixups(this.fixups);
},
};
render: function() {
render() {
return (
<object className={"mx_TintableSvg " + (this.props.className ? this.props.className : "")}
type="image/svg+xml"
@ -70,8 +65,8 @@ const TintableSvg = createReactClass({
tabIndex="-1"
/>
);
},
});
}
}
// Register with the Tinter so that we will be told if the tint changes
Tinter.registerTintable(function() {

View file

@ -16,31 +16,26 @@ limitations under the License.
*/
import React from 'react';
import createReactClass from 'create-react-class';
import * as sdk from '../../../index';
export default createReactClass({
displayName: 'TooltipButton',
export default class TooltipButton extends React.Component {
state = {
hover: false,
};
getInitialState: function() {
return {
hover: false,
};
},
onMouseOver: function() {
onMouseOver = () => {
this.setState({
hover: true,
});
},
};
onMouseLeave: function() {
onMouseLeave = () => {
this.setState({
hover: false,
});
},
};
render: function() {
render() {
const Tooltip = sdk.getComponent("elements.Tooltip");
const tip = this.state.hover ? <Tooltip
className="mx_TooltipButton_container"
@ -53,5 +48,5 @@ export default createReactClass({
{ tip }
</div>
);
},
});
}
}

View file

@ -17,13 +17,10 @@ limitations under the License.
import React from 'react';
import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import { _t } from '../../../languageHandler';
export default createReactClass({
displayName: 'TruncatedList',
propTypes: {
export default class TruncatedList extends React.Component {
static propTypes = {
// The number of elements to show before truncating. If negative, no truncation is done.
truncateAt: PropTypes.number,
// The className to apply to the wrapping div
@ -40,20 +37,18 @@ export default createReactClass({
// A function which will be invoked when an overflow element is required.
// This will be inserted after the children.
createOverflowElement: PropTypes.func,
},
};
getDefaultProps: function() {
return {
truncateAt: 2,
createOverflowElement: function(overflowCount, totalCount) {
return (
<div>{ _t("And %(count)s more...", {count: overflowCount}) }</div>
);
},
};
},
static defaultProps ={
truncateAt: 2,
createOverflowElement(overflowCount, totalCount) {
return (
<div>{ _t("And %(count)s more...", {count: overflowCount}) }</div>
);
},
};
_getChildren: function(start, end) {
_getChildren(start, end) {
if (this.props.getChildren && this.props.getChildCount) {
return this.props.getChildren(start, end);
} else {
@ -64,9 +59,9 @@ export default createReactClass({
return c != null;
}).slice(start, end);
}
},
}
_getChildCount: function() {
_getChildCount() {
if (this.props.getChildren && this.props.getChildCount) {
return this.props.getChildCount();
} else {
@ -74,9 +69,9 @@ export default createReactClass({
return c != null;
}).length;
}
},
}
render: function() {
render() {
let overflowNode = null;
const totalChildren = this._getChildCount();
@ -98,5 +93,5 @@ export default createReactClass({
{ overflowNode }
</div>
);
},
});
}
}

View file

@ -18,7 +18,6 @@ limitations under the License.
import React from 'react';
import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import * as sdk from '../../../index';
import dis from '../../../dispatcher/dispatcher';
import {_t} from '../../../languageHandler';
@ -29,50 +28,48 @@ import MatrixClientContext from "../../../contexts/MatrixClientContext";
import {RovingTabIndexWrapper} from "../../../accessibility/RovingTabIndex";
// XXX this class copies a lot from RoomTile.js
export default createReactClass({
displayName: 'GroupInviteTile',
propTypes: {
export default class GroupInviteTile extends React.Component {
static propTypes: {
group: PropTypes.object.isRequired,
},
};
statics: {
contextType: MatrixClientContext,
},
static contextType = MatrixClientContext;
getInitialState: function() {
return ({
constructor(props, context) {
super(props, context);
this.state = {
hover: false,
badgeHover: false,
menuDisplayed: false,
selected: this.props.group.groupId === null, // XXX: this needs linking to LoggedInView/GroupView state
});
},
};
}
onClick: function(e) {
onClick = e => {
dis.dispatch({
action: 'view_group',
group_id: this.props.group.groupId,
});
},
};
onMouseEnter: function() {
onMouseEnter = () => {
const state = {hover: true};
// Only allow non-guests to access the context menu
if (!this.context.isGuest()) {
state.badgeHover = true;
}
this.setState(state);
},
};
onMouseLeave: function() {
onMouseLeave = () => {
this.setState({
badgeHover: false,
hover: false,
});
},
};
_showContextMenu: function(boundingClientRect) {
_showContextMenu(boundingClientRect) {
// Only allow non-guests to access the context menu
if (MatrixClientPeg.get().isGuest()) return;
@ -86,17 +83,17 @@ export default createReactClass({
}
this.setState(state);
},
}
onContextMenuButtonClick: function(e) {
onContextMenuButtonClick = e => {
// Prevent the RoomTile onClick event firing as well
e.stopPropagation();
e.preventDefault();
this._showContextMenu(e.target.getBoundingClientRect());
},
};
onContextMenu: function(e) {
onContextMenu = e => {
// Prevent the native context menu
e.preventDefault();
@ -105,15 +102,15 @@ export default createReactClass({
top: e.clientY,
height: 0,
});
},
};
closeMenu: function() {
closeMenu = () => {
this.setState({
contextMenuPosition: null,
});
},
};
render: function() {
render() {
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
const BaseAvatar = sdk.getComponent('avatars.BaseAvatar');
@ -197,5 +194,5 @@ export default createReactClass({
{ contextMenu }
</React.Fragment>;
},
});
}
}

View file

@ -16,7 +16,6 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import createReactClass from 'create-react-class';
import { _t } from '../../../languageHandler';
import * as sdk from '../../../index';
import dis from '../../../dispatcher/dispatcher';
@ -30,33 +29,29 @@ import {Action} from "../../../dispatcher/actions";
const INITIAL_LOAD_NUM_MEMBERS = 30;
export default createReactClass({
displayName: 'GroupMemberList',
propTypes: {
export default class GroupMemberList extends React.Component {
static propTypes = {
groupId: PropTypes.string.isRequired,
},
};
getInitialState: function() {
return {
members: null,
membersError: null,
invitedMembers: null,
invitedMembersError: null,
truncateAt: INITIAL_LOAD_NUM_MEMBERS,
};
},
state = {
members: null,
membersError: null,
invitedMembers: null,
invitedMembersError: null,
truncateAt: INITIAL_LOAD_NUM_MEMBERS,
};
componentDidMount: function() {
componentDidMount() {
this._unmounted = false;
this._initGroupStore(this.props.groupId);
},
}
componentWillUnmount: function() {
componentWillUnmount() {
this._unmounted = true;
},
}
_initGroupStore: function(groupId) {
_initGroupStore(groupId) {
GroupStore.registerListener(groupId, () => {
this._fetchMembers();
});
@ -73,17 +68,17 @@ export default createReactClass({
});
}
});
},
}
_fetchMembers: function() {
_fetchMembers() {
if (this._unmounted) return;
this.setState({
members: GroupStore.getGroupMembers(this.props.groupId),
invitedMembers: GroupStore.getGroupInvitedMembers(this.props.groupId),
});
},
}
_createOverflowTile: function(overflowCount, totalCount) {
_createOverflowTile = (overflowCount, totalCount) => {
// For now we'll pretend this is any entity. It should probably be a separate tile.
const EntityTile = sdk.getComponent("rooms.EntityTile");
const BaseAvatar = sdk.getComponent("avatars.BaseAvatar");
@ -94,19 +89,19 @@ export default createReactClass({
} name={text} presenceState="online" suppressOnHover={true}
onClick={this._showFullMemberList} />
);
},
};
_showFullMemberList: function() {
_showFullMemberList = () => {
this.setState({
truncateAt: -1,
});
},
};
onSearchQueryChanged: function(ev) {
onSearchQueryChanged = ev => {
this.setState({ searchQuery: ev.target.value });
},
};
makeGroupMemberTiles: function(query, memberList, memberListError) {
makeGroupMemberTiles(query, memberList, memberListError) {
if (memberListError) {
return <div className="warning">{ _t("Failed to load group members") }</div>;
}
@ -160,9 +155,9 @@ export default createReactClass({
>
{ memberTiles }
</TruncatedList>;
},
}
onInviteToGroupButtonClick() {
onInviteToGroupButtonClick = () => {
showGroupInviteDialog(this.props.groupId).then(() => {
dis.dispatch({
action: Action.SetRightPanelPhase,
@ -170,9 +165,9 @@ export default createReactClass({
refireParams: { groupId: this.props.groupId },
});
});
},
};
render: function() {
render() {
if (this.state.fetching || this.state.fetchingInvitedMembers) {
const Spinner = sdk.getComponent("elements.Spinner");
return (<div className="mx_MemberList">
@ -230,5 +225,5 @@ export default createReactClass({
{ inputBox }
</div>
);
},
});
}
}

View file

@ -18,37 +18,28 @@ limitations under the License.
import React from 'react';
import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import * as sdk from '../../../index';
import dis from '../../../dispatcher/dispatcher';
import { GroupMemberType } from '../../../groups';
import MatrixClientContext from "../../../contexts/MatrixClientContext";
export default createReactClass({
displayName: 'GroupMemberTile',
propTypes: {
export default class GroupMemberTile extends React.Component {
static propTypes = {
groupId: PropTypes.string.isRequired,
member: GroupMemberType.isRequired,
},
};
getInitialState: function() {
return {};
},
static contextType = MatrixClientContext;
statics: {
contextType: MatrixClientContext,
},
onClick: function(e) {
onClick = e => {
dis.dispatch({
action: 'view_group_user',
member: this.props.member,
groupId: this.props.groupId,
});
},
};
render: function() {
render() {
const BaseAvatar = sdk.getComponent('avatars.BaseAvatar');
const EntityTile = sdk.getComponent('rooms.EntityTile');
@ -74,5 +65,5 @@ export default createReactClass({
powerStatus={this.props.member.isPrivileged ? EntityTile.POWER_STATUS_ADMIN : null}
/>
);
},
});
}
}

View file

@ -16,44 +16,39 @@ limitations under the License.
import React from 'react';
import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import * as sdk from '../../../index';
import GroupStore from '../../../stores/GroupStore';
import ToggleSwitch from "../elements/ToggleSwitch";
export default createReactClass({
displayName: 'GroupPublicityToggle',
propTypes: {
export default class GroupPublicityToggle extends React.Component {
static propTypes = {
groupId: PropTypes.string.isRequired,
},
};
getInitialState() {
return {
busy: false,
ready: false,
isGroupPublicised: false, // assume false as <ToggleSwitch /> expects a boolean
};
},
state = {
busy: false,
ready: false,
isGroupPublicised: false, // assume false as <ToggleSwitch /> expects a boolean
};
componentDidMount: function() {
componentDidMount() {
this._initGroupStore(this.props.groupId);
},
}
_initGroupStore: function(groupId) {
_initGroupStore(groupId) {
this._groupStoreToken = GroupStore.registerListener(groupId, () => {
this.setState({
isGroupPublicised: Boolean(GroupStore.getGroupPublicity(groupId)),
ready: GroupStore.isStateReady(groupId, GroupStore.STATE_KEY.Summary),
});
});
},
}
componentWillUnmount() {
if (this._groupStoreToken) this._groupStoreToken.unregister();
},
}
_onPublicityToggle: function() {
_onPublicityToggle = () => {
this.setState({
busy: true,
// Optimistic early update
@ -64,7 +59,7 @@ export default createReactClass({
busy: false,
});
});
},
};
render() {
const GroupTile = sdk.getComponent('groups.GroupTile');
@ -76,5 +71,5 @@ export default createReactClass({
disabled={!this.state.ready || this.state.busy}
onChange={this._onPublicityToggle} />
</div>;
},
});
}
}

View file

@ -17,7 +17,6 @@ limitations under the License.
import React from 'react';
import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import dis from '../../../dispatcher/dispatcher';
import Modal from '../../../Modal';
import * as sdk from '../../../index';
@ -26,50 +25,45 @@ import GroupStore from '../../../stores/GroupStore';
import MatrixClientContext from "../../../contexts/MatrixClientContext";
import AutoHideScrollbar from "../../structures/AutoHideScrollbar";
export default createReactClass({
displayName: 'GroupRoomInfo',
export default class GroupRoomInfo extends React.Component {
static contextType = MatrixClientContext;
statics: {
contextType: MatrixClientContext,
},
propTypes: {
static propTypes = {
groupId: PropTypes.string,
groupRoomId: PropTypes.string,
},
};
getInitialState: function() {
return {
isUserPrivilegedInGroup: null,
groupRoom: null,
groupRoomPublicityLoading: false,
groupRoomRemoveLoading: false,
};
},
state = {
isUserPrivilegedInGroup: null,
groupRoom: null,
groupRoomPublicityLoading: false,
groupRoomRemoveLoading: false,
};
componentDidMount: function() {
componentDidMount() {
this._initGroupStore(this.props.groupId);
},
}
// TODO: [REACT-WARNING] Replace with appropriate lifecycle event
// eslint-disable-next-line camelcase
UNSAFE_componentWillReceiveProps(newProps) {
if (newProps.groupId !== this.props.groupId) {
this._unregisterGroupStore(this.props.groupId);
this._initGroupStore(newProps.groupId);
}
},
}
componentWillUnmount() {
this._unregisterGroupStore(this.props.groupId);
},
}
_initGroupStore(groupId) {
GroupStore.registerListener(groupId, this.onGroupStoreUpdated);
},
}
_unregisterGroupStore(groupId) {
GroupStore.unregisterListener(this.onGroupStoreUpdated);
},
}
_updateGroupRoom() {
this.setState({
@ -77,16 +71,16 @@ export default createReactClass({
(r) => r.roomId === this.props.groupRoomId,
),
});
},
}
onGroupStoreUpdated: function() {
onGroupStoreUpdated = () => {
this.setState({
isUserPrivilegedInGroup: GroupStore.isUserPrivileged(this.props.groupId),
});
this._updateGroupRoom();
},
};
_onRemove: function(e) {
_onRemove = e => {
const groupId = this.props.groupId;
const roomName = this.state.groupRoom.displayname;
e.preventDefault();
@ -119,15 +113,15 @@ export default createReactClass({
});
},
});
},
};
_onCancel: function(e) {
_onCancel = e => {
dis.dispatch({
action: "view_group_room_list",
});
},
};
_changeGroupRoomPublicity(e) {
_changeGroupRoomPublicity = e => {
const isPublic = e.target.value === "public";
this.setState({
groupRoomPublicityLoading: true,
@ -150,9 +144,9 @@ export default createReactClass({
groupRoomPublicityLoading: false,
});
});
},
};
render: function() {
render() {
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
const InlineSpinner = sdk.getComponent('elements.InlineSpinner');
if (this.state.groupRoomRemoveLoading || !this.state.groupRoom) {
@ -235,5 +229,5 @@ export default createReactClass({
</AutoHideScrollbar>
</div>
);
},
});
}
}

View file

@ -14,7 +14,6 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import createReactClass from 'create-react-class';
import { _t } from '../../../languageHandler';
import * as sdk from '../../../index';
import GroupStore from '../../../stores/GroupStore';
@ -25,34 +24,32 @@ import AutoHideScrollbar from "../../structures/AutoHideScrollbar";
const INITIAL_LOAD_NUM_ROOMS = 30;
export default createReactClass({
propTypes: {
export default class GroupRoomList extends React.Component {
static propTypes = {
groupId: PropTypes.string.isRequired,
},
};
getInitialState: function() {
return {
rooms: null,
truncateAt: INITIAL_LOAD_NUM_ROOMS,
searchQuery: "",
};
},
state = {
rooms: null,
truncateAt: INITIAL_LOAD_NUM_ROOMS,
searchQuery: "",
};
componentDidMount: function() {
componentDidMount() {
this._unmounted = false;
this._initGroupStore(this.props.groupId);
},
}
componentWillUnmount() {
this._unmounted = true;
this._unregisterGroupStore();
},
}
_unregisterGroupStore() {
GroupStore.unregisterListener(this.onGroupStoreUpdated);
},
}
_initGroupStore: function(groupId) {
_initGroupStore(groupId) {
GroupStore.registerListener(groupId, this.onGroupStoreUpdated);
// XXX: This should be more fluxy - let's get the error from GroupStore .getError or something
// XXX: This is also leaked - we should remove it when unmounting
@ -62,16 +59,16 @@ export default createReactClass({
rooms: null,
});
});
},
}
onGroupStoreUpdated: function() {
onGroupStoreUpdated = () => {
if (this._unmounted) return;
this.setState({
rooms: GroupStore.getGroupRooms(this.props.groupId),
});
},
};
_createOverflowTile: function(overflowCount, totalCount) {
_createOverflowTile = (overflowCount, totalCount) => {
// For now we'll pretend this is any entity. It should probably be a separate tile.
const EntityTile = sdk.getComponent("rooms.EntityTile");
const BaseAvatar = sdk.getComponent("avatars.BaseAvatar");
@ -82,25 +79,25 @@ export default createReactClass({
} name={text} presenceState="online" suppressOnHover={true}
onClick={this._showFullRoomList} />
);
},
};
_showFullRoomList: function() {
_showFullRoomList = () => {
this.setState({
truncateAt: -1,
});
},
};
onSearchQueryChanged: function(ev) {
onSearchQueryChanged = ev => {
this.setState({ searchQuery: ev.target.value });
},
};
onAddRoomToGroupButtonClick() {
onAddRoomToGroupButtonClick = () => {
showGroupAddRoomDialog(this.props.groupId).then(() => {
this.forceUpdate();
});
},
};
makeGroupRoomTiles: function(query) {
makeGroupRoomTiles(query) {
const GroupRoomTile = sdk.getComponent("groups.GroupRoomTile");
query = (query || "").toLowerCase();
@ -123,9 +120,9 @@ export default createReactClass({
});
return roomList;
},
}
render: function() {
render() {
if (this.state.rooms === null) {
return null;
}
@ -160,5 +157,5 @@ export default createReactClass({
{ inputBox }
</div>
);
},
});
}
}

View file

@ -16,29 +16,28 @@ limitations under the License.
import React from 'react';
import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import * as sdk from '../../../index';
import dis from '../../../dispatcher/dispatcher';
import { GroupRoomType } from '../../../groups';
import MatrixClientContext from "../../../contexts/MatrixClientContext";
const GroupRoomTile = createReactClass({
displayName: 'GroupRoomTile',
propTypes: {
class GroupRoomTile extends React.Component {
static propTypes = {
groupId: PropTypes.string.isRequired,
groupRoom: GroupRoomType.isRequired,
},
};
onClick: function(e) {
static contextType = MatrixClientContext
onClick = e => {
dis.dispatch({
action: 'view_group_room',
groupId: this.props.groupId,
groupRoomId: this.props.groupRoom.roomId,
});
},
};
render: function() {
render() {
const BaseAvatar = sdk.getComponent('avatars.BaseAvatar');
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
const avatarUrl = this.context.mxcUrlToHttp(
@ -63,10 +62,7 @@ const GroupRoomTile = createReactClass({
</div>
</AccessibleButton>
);
},
});
GroupRoomTile.contextType = MatrixClientContext;
}
}
export default GroupRoomTile;

View file

@ -16,7 +16,6 @@ limitations under the License.
import React from 'react';
import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import { Draggable, Droppable } from 'react-beautiful-dnd';
import * as sdk from '../../../index';
import dis from '../../../dispatcher/dispatcher';
@ -25,53 +24,45 @@ import MatrixClientContext from "../../../contexts/MatrixClientContext";
function nop() {}
const GroupTile = createReactClass({
displayName: 'GroupTile',
propTypes: {
class GroupTile extends React.Component {
static propTypes = {
groupId: PropTypes.string.isRequired,
// Whether to show the short description of the group on the tile
showDescription: PropTypes.bool,
// Height of the group avatar in pixels
avatarHeight: PropTypes.number,
draggable: PropTypes.bool,
},
};
statics: {
contextType: MatrixClientContext,
},
static contextType = MatrixClientContext;
getInitialState() {
return {
profile: null,
};
},
static defaultProps = {
showDescription: true,
avatarHeight: 50,
draggable: true,
};
getDefaultProps() {
return {
showDescription: true,
avatarHeight: 50,
draggable: true,
};
},
state = {
profile: null,
};
componentDidMount: function() {
componentDidMount() {
FlairStore.getGroupProfileCached(this.context, this.props.groupId).then((profile) => {
this.setState({profile});
}).catch((err) => {
console.error('Error whilst getting cached profile for GroupTile', err);
});
},
}
onMouseDown: function(e) {
onMouseDown = e => {
e.preventDefault();
dis.dispatch({
action: 'view_group',
group_id: this.props.groupId,
});
},
};
render: function() {
render() {
const BaseAvatar = sdk.getComponent('avatars.BaseAvatar');
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
const profile = this.state.profile || {};
@ -135,7 +126,7 @@ const GroupTile = createReactClass({
<div className="mx_GroupTile_groupId">{ this.props.groupId }</div>
</div>
</AccessibleButton>;
},
});
}
}
export default GroupTile;

View file

@ -15,33 +15,26 @@ limitations under the License.
*/
import React from 'react';
import createReactClass from 'create-react-class';
import * as sdk from '../../../index';
import { _t } from '../../../languageHandler';
import MatrixClientContext from "../../../contexts/MatrixClientContext";
export default createReactClass({
displayName: 'GroupUserSettings',
export default class GroupUserSettings extends React.Component {
static contextType = MatrixClientContext;
statics: {
contextType: MatrixClientContext,
},
state = {
error: null,
groups: null,
};
getInitialState() {
return {
error: null,
groups: null,
};
},
componentDidMount: function() {
componentDidMount() {
this.context.getJoinedGroups().then((result) => {
this.setState({groups: result.groups || [], error: null});
}, (err) => {
console.error(err);
this.setState({groups: null, error: err});
});
},
}
render() {
let text = "";
@ -70,5 +63,5 @@ export default createReactClass({
</div>
</div>
);
},
});
}
}

View file

@ -17,7 +17,6 @@ limitations under the License.
import React, {createRef} from 'react';
import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import filesize from 'filesize';
import {MatrixClientPeg} from '../../../MatrixClientPeg';
import * as sdk from '../../../index';
@ -117,16 +116,8 @@ function computedStyle(element) {
return cssText;
}
export default createReactClass({
displayName: 'MFileBody',
getInitialState: function() {
return {
decryptedBlob: (this.props.decryptedBlob ? this.props.decryptedBlob : null),
};
},
propTypes: {
export default class MFileBody extends React.Component {
static propTypes = {
/* the MatrixEvent to show */
mxEvent: PropTypes.object.isRequired,
/* already decrypted blob */
@ -135,7 +126,19 @@ export default createReactClass({
onHeightChanged: PropTypes.func,
/* the shape of the tile, used */
tileShape: PropTypes.string,
},
};
constructor(props) {
super(props);
this.state = {
decryptedBlob: (this.props.decryptedBlob ? this.props.decryptedBlob : null),
};
this._iframe = createRef();
this._dummyLink = createRef();
this._downloadImage = createRef();
}
/**
* Extracts a human readable label for the file attachment to use as
@ -144,7 +147,7 @@ export default createReactClass({
* @params {Object} content The "content" key of the matrix event.
* @return {string} the human readable link text for the attachment.
*/
presentableTextForFile: function(content) {
presentableTextForFile(content) {
let linkText = _t("Attachment");
if (content.body && content.body.length > 0) {
// The content body should be the name of the file including a
@ -163,40 +166,33 @@ export default createReactClass({
linkText += ' (' + filesize(content.info.size) + ')';
}
return linkText;
},
}
_getContentUrl: function() {
_getContentUrl() {
const content = this.props.mxEvent.getContent();
return MatrixClientPeg.get().mxcUrlToHttp(content.url);
},
}
// TODO: [REACT-WARNING] Replace component with real class, use constructor for refs
UNSAFE_componentWillMount: function() {
this._iframe = createRef();
this._dummyLink = createRef();
this._downloadImage = createRef();
},
componentDidMount: function() {
componentDidMount() {
// Add this to the list of mounted components to receive notifications
// when the tint changes.
this.id = nextMountId++;
mounts[this.id] = this;
this.tint();
},
}
componentDidUpdate: function(prevProps, prevState) {
componentDidUpdate(prevProps, prevState) {
if (this.props.onHeightChanged && !prevState.decryptedBlob && this.state.decryptedBlob) {
this.props.onHeightChanged();
}
},
}
componentWillUnmount: function() {
componentWillUnmount() {
// Remove this from the list of mounted components
delete mounts[this.id];
},
}
tint: function() {
tint = () => {
// Update our tinted copy of require("../../../../res/img/download.svg")
if (this._downloadImage.current) {
this._downloadImage.current.src = tintedDownloadImageURL;
@ -210,9 +206,9 @@ export default createReactClass({
style: computedStyle(this._dummyLink.current),
}, "*");
}
},
};
render: function() {
render() {
const content = this.props.mxEvent.getContent();
const text = this.presentableTextForFile(content);
const isEncrypted = content.file !== undefined;
@ -378,5 +374,5 @@ export default createReactClass({
{ _t("Invalid file%(extra)s", { extra: extra }) }
</span>;
}
},
});
}
}

View file

@ -17,7 +17,6 @@ limitations under the License.
import React from 'react';
import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import MFileBody from './MFileBody';
import {MatrixClientPeg} from '../../../MatrixClientPeg';
import { decryptFile } from '../../../utils/DecryptFile';
@ -25,27 +24,23 @@ import { _t } from '../../../languageHandler';
import SettingsStore from "../../../settings/SettingsStore";
import InlineSpinner from '../elements/InlineSpinner';
export default createReactClass({
displayName: 'MVideoBody',
propTypes: {
export default class MVideoBody extends React.Component {
static propTypes = {
/* the MatrixEvent to show */
mxEvent: PropTypes.object.isRequired,
/* called when the video has loaded */
onHeightChanged: PropTypes.func.isRequired,
},
};
getInitialState: function() {
return {
decryptedUrl: null,
decryptedThumbnailUrl: null,
decryptedBlob: null,
error: null,
};
},
state = {
decryptedUrl: null,
decryptedThumbnailUrl: null,
decryptedBlob: null,
error: null,
};
thumbScale: function(fullWidth, fullHeight, thumbWidth, thumbHeight) {
thumbScale(fullWidth, fullHeight, thumbWidth, thumbHeight) {
if (!fullWidth || !fullHeight) {
// Cannot calculate thumbnail height for image: missing w/h in metadata. We can't even
// log this because it's spammy
@ -64,18 +59,18 @@ export default createReactClass({
// height is the dominant dimension so scaling will be fixed on that
return heightMulti;
}
},
}
_getContentUrl: function() {
_getContentUrl() {
const content = this.props.mxEvent.getContent();
if (content.file !== undefined) {
return this.state.decryptedUrl;
} else {
return MatrixClientPeg.get().mxcUrlToHttp(content.url);
}
},
}
_getThumbUrl: function() {
_getThumbUrl() {
const content = this.props.mxEvent.getContent();
if (content.file !== undefined) {
return this.state.decryptedThumbnailUrl;
@ -84,9 +79,9 @@ export default createReactClass({
} else {
return null;
}
},
}
componentDidMount: function() {
componentDidMount() {
const content = this.props.mxEvent.getContent();
if (content.file !== undefined && this.state.decryptedUrl === null) {
let thumbnailPromise = Promise.resolve(null);
@ -118,18 +113,18 @@ export default createReactClass({
});
});
}
},
}
componentWillUnmount: function() {
componentWillUnmount() {
if (this.state.decryptedUrl) {
URL.revokeObjectURL(this.state.decryptedUrl);
}
if (this.state.decryptedThumbnailUrl) {
URL.revokeObjectURL(this.state.decryptedThumbnailUrl);
}
},
}
render: function() {
render() {
const content = this.props.mxEvent.getContent();
if (this.state.error !== null) {
@ -182,5 +177,5 @@ export default createReactClass({
<MFileBody {...this.props} decryptedBlob={this.state.decryptedBlob} />
</span>
);
},
});
}
}

View file

@ -16,17 +16,14 @@ limitations under the License.
import React, {createRef} from 'react';
import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import * as sdk from '../../../index';
import SettingsStore from "../../../settings/SettingsStore";
import {Mjolnir} from "../../../mjolnir/Mjolnir";
import RedactedBody from "./RedactedBody";
import UnknownBody from "./UnknownBody";
export default createReactClass({
displayName: 'MessageEvent',
propTypes: {
export default class MessageEvent extends React.Component {
static propTypes = {
/* the MatrixEvent to show */
mxEvent: PropTypes.object.isRequired,
@ -47,22 +44,23 @@ export default createReactClass({
/* the maximum image height to use, if the event is an image */
maxImageHeight: PropTypes.number,
},
};
constructor(props) {
super(props);
// TODO: [REACT-WARNING] Replace component with real class, use constructor for refs
UNSAFE_componentWillMount: function() {
this._body = createRef();
},
}
getEventTileOps: function() {
getEventTileOps = () => {
return this._body.current && this._body.current.getEventTileOps ? this._body.current.getEventTileOps() : null;
},
};
onTileUpdate: function() {
onTileUpdate = () => {
this.forceUpdate();
},
};
render: function() {
render() {
const bodyTypes = {
'm.text': sdk.getComponent('messages.TextualBody'),
'm.notice': sdk.getComponent('messages.TextualBody'),
@ -123,5 +121,5 @@ export default createReactClass({
onHeightChanged={this.props.onHeightChanged}
onMessageAllowed={this.onTileUpdate}
/>;
},
});
}
}

View file

@ -18,22 +18,19 @@ limitations under the License.
import React from 'react';
import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import {MatrixClientPeg} from '../../../MatrixClientPeg';
import { _t } from '../../../languageHandler';
import * as sdk from '../../../index';
import Modal from '../../../Modal';
import AccessibleButton from '../elements/AccessibleButton';
export default createReactClass({
displayName: 'RoomAvatarEvent',
propTypes: {
export default class RoomAvatarEvent extends React.Component {
static propTypes = {
/* the MatrixEvent to show */
mxEvent: PropTypes.object.isRequired,
},
};
onAvatarClick: function() {
onAvatarClick = () => {
const cli = MatrixClientPeg.get();
const ev = this.props.mxEvent;
const httpUrl = cli.mxcUrlToHttp(ev.getContent().url);
@ -50,9 +47,9 @@ export default createReactClass({
name: text,
};
Modal.createDialog(ImageView, params, "mx_Dialog_lightbox");
},
};
render: function() {
render() {
const ev = this.props.mxEvent;
const senderDisplayName = ev.sender && ev.sender.name ? ev.sender.name : ev.getSender();
const RoomAvatar = sdk.getComponent("avatars.RoomAvatar");
@ -86,5 +83,5 @@ export default createReactClass({
}
</div>
);
},
});
}
}

View file

@ -17,22 +17,19 @@ limitations under the License.
import React from 'react';
import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import dis from '../../../dispatcher/dispatcher';
import { RoomPermalinkCreator } from '../../../utils/permalinks/Permalinks';
import { _t } from '../../../languageHandler';
import {MatrixClientPeg} from '../../../MatrixClientPeg';
export default createReactClass({
displayName: 'RoomCreate',
propTypes: {
export default class RoomCreate extends React.Component {
static propTypes = {
/* the MatrixEvent to show */
mxEvent: PropTypes.object.isRequired,
},
};
_onLinkClicked: function(e) {
_onLinkClicked = e => {
e.preventDefault();
const predecessor = this.props.mxEvent.getContent()['predecessor'];
@ -43,9 +40,9 @@ export default createReactClass({
highlighted: true,
room_id: predecessor['room_id'],
});
},
};
render: function() {
render() {
const predecessor = this.props.mxEvent.getContent()['predecessor'];
if (predecessor === undefined) {
return <div />; // We should never have been instaniated in this case
@ -66,5 +63,5 @@ export default createReactClass({
{_t("Click here to see older messages.")}
</a>
</div>;
},
});
}
}

View file

@ -16,31 +16,25 @@
import React from 'react';
import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import Flair from '../elements/Flair.js';
import FlairStore from '../../../stores/FlairStore';
import { _t } from '../../../languageHandler';
import {getUserNameColorClass} from '../../../utils/FormattingUtils';
import MatrixClientContext from "../../../contexts/MatrixClientContext";
export default createReactClass({
displayName: 'SenderProfile',
propTypes: {
export default class SenderProfile extends React.Component {
static propTypes = {
mxEvent: PropTypes.object.isRequired, // event whose sender we're showing
text: PropTypes.string, // Text to show. Defaults to sender name
onClick: PropTypes.func,
},
};
statics: {
contextType: MatrixClientContext,
},
static contextType = MatrixClientContext;
getInitialState() {
return {
userGroups: null,
relatedGroups: [],
};
},
state = {
userGroups: null,
relatedGroups: [],
};
componentDidMount() {
this.unmounted = false;
@ -54,20 +48,20 @@ export default createReactClass({
});
this.context.on('RoomState.events', this.onRoomStateEvents);
},
}
componentWillUnmount() {
this.unmounted = true;
this.context.removeListener('RoomState.events', this.onRoomStateEvents);
},
}
onRoomStateEvents(event) {
onRoomStateEvents = event => {
if (event.getType() === 'm.room.related_groups' &&
event.getRoomId() === this.props.mxEvent.getRoomId()
) {
this._updateRelatedGroups();
}
},
};
_updateRelatedGroups() {
if (this.unmounted) return;
@ -78,7 +72,7 @@ export default createReactClass({
this.setState({
relatedGroups: relatedGroupsEvent ? relatedGroupsEvent.getContent().groups || [] : [],
});
},
}
_getDisplayedGroups(userGroups, relatedGroups) {
let displayedGroups = userGroups || [];
@ -90,7 +84,7 @@ export default createReactClass({
displayedGroups = [];
}
return displayedGroups;
},
}
render() {
const {mxEvent} = this.props;
@ -138,5 +132,5 @@ export default createReactClass({
</div>
</div>
);
},
});
}
}

View file

@ -19,7 +19,6 @@ limitations under the License.
import React, {createRef} from 'react';
import ReactDOM from 'react-dom';
import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import highlight from 'highlight.js';
import * as HtmlUtils from '../../../HtmlUtils';
import {formatDate} from '../../../DateUtils';
@ -37,10 +36,8 @@ import {toRightOf} from "../../structures/ContextMenu";
import {copyPlaintext} from "../../../utils/strings";
import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";
export default createReactClass({
displayName: 'TextualBody',
propTypes: {
export default class TextualBody extends React.Component {
static propTypes = {
/* the MatrixEvent to show */
mxEvent: PropTypes.object.isRequired,
@ -58,10 +55,14 @@ export default createReactClass({
/* the shape of the tile, used */
tileShape: PropTypes.string,
},
};
getInitialState: function() {
return {
constructor(props) {
super(props);
this._content = createRef();
this.state = {
// the URLs (if any) to be previewed with a LinkPreviewWidget
// inside this TextualBody.
links: [],
@ -69,20 +70,15 @@ export default createReactClass({
// track whether the preview widget is hidden
widgetHidden: false,
};
},
}
// TODO: [REACT-WARNING] Replace component with real class, use constructor for refs
UNSAFE_componentWillMount: function() {
this._content = createRef();
},
componentDidMount: function() {
componentDidMount() {
this._unmounted = false;
this._pills = [];
if (!this.props.editState) {
this._applyFormatting();
}
},
}
_applyFormatting() {
this.activateSpoilers([this._content.current]);
@ -119,9 +115,9 @@ export default createReactClass({
}
this._addCodeCopyButton();
}
},
}
componentDidUpdate: function(prevProps) {
componentDidUpdate(prevProps) {
if (!this.props.editState) {
const stoppedEditing = prevProps.editState && !this.props.editState;
const messageWasEdited = prevProps.replacingEventId !== this.props.replacingEventId;
@ -129,14 +125,14 @@ export default createReactClass({
this._applyFormatting();
}
}
},
}
componentWillUnmount: function() {
componentWillUnmount() {
this._unmounted = true;
unmountPills(this._pills);
},
}
shouldComponentUpdate: function(nextProps, nextState) {
shouldComponentUpdate(nextProps, nextState) {
//console.info("shouldComponentUpdate: ShowUrlPreview for %s is %s", this.props.mxEvent.getId(), this.props.showUrlPreview);
// exploit that events are immutable :)
@ -148,9 +144,9 @@ export default createReactClass({
nextProps.editState !== this.props.editState ||
nextState.links !== this.state.links ||
nextState.widgetHidden !== this.state.widgetHidden);
},
}
calculateUrlPreview: function() {
calculateUrlPreview() {
//console.info("calculateUrlPreview: ShowUrlPreview for %s is %s", this.props.mxEvent.getId(), this.props.showUrlPreview);
if (this.props.showUrlPreview) {
@ -176,9 +172,9 @@ export default createReactClass({
this.setState({ links: [] });
}
}
},
}
activateSpoilers: function(nodes) {
activateSpoilers(nodes) {
let node = nodes[0];
while (node) {
if (node.tagName === "SPAN" && typeof node.getAttribute("data-mx-spoiler") === "string") {
@ -204,9 +200,9 @@ export default createReactClass({
node = node.nextSibling;
}
},
}
findLinks: function(nodes) {
findLinks(nodes) {
let links = [];
for (let i = 0; i < nodes.length; i++) {
@ -223,9 +219,9 @@ export default createReactClass({
}
}
return links;
},
}
isLinkPreviewable: function(node) {
isLinkPreviewable(node) {
// don't try to preview relative links
if (!node.getAttribute("href").startsWith("http://") &&
!node.getAttribute("href").startsWith("https://")) {
@ -256,7 +252,7 @@ export default createReactClass({
return true;
}
}
},
}
_addCodeCopyButton() {
// Add 'copy' buttons to pre blocks
@ -288,41 +284,39 @@ export default createReactClass({
div.appendChild(p);
div.appendChild(button);
});
},
}
onCancelClick: function(event) {
onCancelClick = event => {
this.setState({ widgetHidden: true });
// FIXME: persist this somewhere smarter than local storage
if (global.localStorage) {
global.localStorage.setItem("hide_preview_" + this.props.mxEvent.getId(), "1");
}
this.forceUpdate();
},
};
onEmoteSenderClick: function(event) {
onEmoteSenderClick = event => {
const mxEvent = this.props.mxEvent;
dis.dispatch({
action: 'insert_mention',
user_id: mxEvent.getSender(),
});
},
};
getEventTileOps: function() {
return {
isWidgetHidden: () => {
return this.state.widgetHidden;
},
getEventTileOps = () => ({
isWidgetHidden: () => {
return this.state.widgetHidden;
},
unhideWidget: () => {
this.setState({ widgetHidden: false });
if (global.localStorage) {
global.localStorage.removeItem("hide_preview_" + this.props.mxEvent.getId());
}
},
};
},
unhideWidget: () => {
this.setState({widgetHidden: false});
if (global.localStorage) {
global.localStorage.removeItem("hide_preview_" + this.props.mxEvent.getId());
}
},
});
onStarterLinkClick: function(starterLink, ev) {
onStarterLinkClick = (starterLink, ev) => {
ev.preventDefault();
// We need to add on our scalar token to the starter link, but we may not have one!
// In addition, we can't fetch one on click and then go to it immediately as that
@ -353,7 +347,7 @@ export default createReactClass({
"Do you wish to continue?", { integrationsUrl: integrationsUrl }) }
</div>,
button: _t("Continue"),
onFinished: function(confirmed) {
onFinished(confirmed) {
if (!confirmed) {
return;
}
@ -367,14 +361,14 @@ export default createReactClass({
},
});
});
},
};
_openHistoryDialog: async function() {
_openHistoryDialog = async () => {
const MessageEditHistoryDialog = sdk.getComponent("views.dialogs.MessageEditHistoryDialog");
Modal.createDialog(MessageEditHistoryDialog, {mxEvent: this.props.mxEvent});
},
};
_renderEditedMarker: function() {
_renderEditedMarker() {
const date = this.props.mxEvent.replacingEventDate();
const dateString = date && formatDate(date);
@ -397,9 +391,9 @@ export default createReactClass({
<span>{`(${_t("edited")})`}</span>
</AccessibleTooltipButton>
);
},
}
render: function() {
render() {
if (this.props.editState) {
const EditMessageComposer = sdk.getComponent('rooms.EditMessageComposer');
return <EditMessageComposer editState={this.props.editState} className="mx_EventTile_content" />;
@ -468,5 +462,5 @@ export default createReactClass({
</span>
);
}
},
});
}
}

View file

@ -17,22 +17,19 @@ limitations under the License.
import React from 'react';
import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import * as TextForEvent from "../../../TextForEvent";
export default createReactClass({
displayName: 'TextualEvent',
propTypes: {
export default class TextualEvent extends React.Component {
static propTypes = {
/* the MatrixEvent to show */
mxEvent: PropTypes.object.isRequired,
},
};
render: function() {
render() {
const text = TextForEvent.textForEvent(this.props.mxEvent);
if (text == null || text.length === 0) return null;
return (
<div className="mx_TextualEvent">{ text }</div>
);
},
});
}
}

View file

@ -41,13 +41,9 @@ interface IProps {}
export default class GroupHeaderButtons extends HeaderButtons {
constructor(props: IProps) {
super(props, HeaderKind.Group);
this.onMembersClicked = this.onMembersClicked.bind(this);
this.onRoomsClicked = this.onRoomsClicked.bind(this);
}
protected onAction(payload: ActionPayload) {
super.onAction(payload);
if (payload.action === Action.ViewUser) {
if ((payload as ViewUserPayload).member) {
this.setPhase(RightPanelPhases.RoomMemberInfo, {member: payload.member});
@ -70,7 +66,7 @@ export default class GroupHeaderButtons extends HeaderButtons {
}
}
private onMembersClicked() {
private onMembersClicked = () => {
if (this.state.phase === RightPanelPhases.GroupMemberInfo) {
// send the active phase to trigger a toggle
this.setPhase(RightPanelPhases.GroupMemberInfo);
@ -78,12 +74,12 @@ export default class GroupHeaderButtons extends HeaderButtons {
// This toggles for us, if needed
this.setPhase(RightPanelPhases.GroupMemberList);
}
}
};
private onRoomsClicked() {
private onRoomsClicked = () => {
// This toggles for us, if needed
this.setPhase(RightPanelPhases.GroupRoomList);
}
};
renderButtons() {
return [

View file

@ -65,9 +65,7 @@ export default abstract class HeaderButtons extends React.Component<IProps, ISta
if (this.dispatcherRef) dis.unregister(this.dispatcherRef);
}
protected onAction(payload) {
// Ignore - intended to be overridden by subclasses
}
protected abstract onAction(payload);
public setPhase(phase: RightPanelPhases, extras?: Partial<SetRightPanelPhaseRefireParams>) {
dis.dispatch<SetRightPanelPhasePayload>({

View file

@ -36,13 +36,9 @@ const MEMBER_PHASES = [
export default class RoomHeaderButtons extends HeaderButtons {
constructor(props) {
super(props, HeaderKind.Room);
this.onMembersClicked = this.onMembersClicked.bind(this);
this.onFilesClicked = this.onFilesClicked.bind(this);
this.onNotificationsClicked = this.onNotificationsClicked.bind(this);
}
protected onAction(payload: ActionPayload) {
super.onAction(payload);
if (payload.action === Action.ViewUser) {
if (payload.member) {
this.setPhase(RightPanelPhases.RoomMemberInfo, {member: payload.member});
@ -58,7 +54,7 @@ export default class RoomHeaderButtons extends HeaderButtons {
}
}
private onMembersClicked() {
private onMembersClicked = () => {
if (this.state.phase === RightPanelPhases.RoomMemberInfo) {
// send the active phase to trigger a toggle
// XXX: we should pass refireParams here but then it won't collapse as we desire it to
@ -67,17 +63,17 @@ export default class RoomHeaderButtons extends HeaderButtons {
// This toggles for us, if needed
this.setPhase(RightPanelPhases.RoomMemberList);
}
}
};
private onFilesClicked() {
private onFilesClicked = () => {
// This toggles for us, if needed
this.setPhase(RightPanelPhases.FilePanel);
}
};
private onNotificationsClicked() {
private onNotificationsClicked = () => {
// This toggles for us, if needed
this.setPhase(RightPanelPhases.NotificationPanel);
}
};
public renderButtons() {
return [

View file

@ -45,6 +45,7 @@ import EncryptionPanel from "./EncryptionPanel";
import { useAsyncMemo } from '../../../hooks/useAsyncMemo';
import { verifyUser, legacyVerifyUser, verifyDevice } from '../../../verification';
import {Action} from "../../../dispatcher/actions";
import {useIsEncrypted} from "../../../hooks/useIsEncrypted";
const _disambiguateDevices = (devices) => {
const names = Object.create(null);
@ -124,18 +125,6 @@ async function openDMForUser(matrixClient, userId) {
createRoom(createRoomOptions);
}
function useIsEncrypted(cli, room) {
const [isEncrypted, setIsEncrypted] = useState(room ? cli.isRoomEncrypted(room.roomId) : undefined);
const update = useCallback((event) => {
if (event.getType() === "m.room.encryption") {
setIsEncrypted(cli.isRoomEncrypted(room.roomId));
}
}, [cli, room]);
useEventEmitter(room ? room.currentState : undefined, "RoomState.events", update);
return isEncrypted;
}
function useHasCrossSigningKeys(cli, member, canVerify, setUpdating) {
return useAsyncMemo(async () => {
if (!canVerify) {
@ -1485,7 +1474,7 @@ const UserInfoHeader = ({onClose, member, e2eStatus}) => {
const UserInfo = ({user, groupId, roomId, onClose, phase=RightPanelPhases.RoomMemberInfo, ...props}) => {
const cli = useContext(MatrixClientContext);
// Load room if we are given a room id and memoize it
// Load room if we are given a room id and memoize it - this can be undefined for User Info/Group Member Info
const room = useMemo(() => roomId ? cli.getRoom(roomId) : null, [cli, roomId]);
// fetch latest room member if we have a room, so we don't show historical information, falling back to user
const member = useMemo(() => room ? (room.getMember(user.userId) || user) : user, [room, user]);

View file

@ -19,7 +19,6 @@ limitations under the License.
import React from 'react';
import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import * as sdk from "../../../index";
import { _t, _td } from '../../../languageHandler';
import SettingsStore from "../../../settings/SettingsStore";
@ -29,20 +28,18 @@ import {Action} from "../../../dispatcher/actions";
import {SettingLevel} from "../../../settings/SettingLevel";
export default createReactClass({
displayName: 'UrlPreviewSettings',
propTypes: {
export default class UrlPreviewSettings extends React.Component {
static propTypes = {
room: PropTypes.object,
},
};
_onClickUserSettings: (e) => {
_onClickUserSettings = (e) => {
e.preventDefault();
e.stopPropagation();
dis.fire(Action.ViewUserSettings);
},
};
render: function() {
render() {
const SettingsFlag = sdk.getComponent("elements.SettingsFlag");
const roomId = this.props.room.roomId;
const isEncrypted = MatrixClientPeg.get().isRoomEncrypted(roomId);
@ -110,5 +107,5 @@ export default createReactClass({
<label>{ previewsForRoomAccount }</label>
</div>
);
},
});
}
}

View file

@ -17,7 +17,6 @@ limitations under the License.
import React, {useState} from 'react';
import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import {MatrixClientPeg} from '../../../MatrixClientPeg';
import AppTile from '../elements/AppTile';
import Modal from '../../../Modal';
@ -38,51 +37,52 @@ import ResizeNotifier from "../../../utils/ResizeNotifier";
// The maximum number of widgets that can be added in a room
const MAX_WIDGETS = 2;
export default createReactClass({
displayName: 'AppsDrawer',
propTypes: {
export default class AppsDrawer extends React.Component {
static propTypes = {
userId: PropTypes.string.isRequired,
room: PropTypes.object.isRequired,
resizeNotifier: PropTypes.instanceOf(ResizeNotifier).isRequired,
showApps: PropTypes.bool, // Should apps be rendered
hide: PropTypes.bool, // If rendered, should apps drawer be visible
},
};
getDefaultProps: () => ({
static defaultProps = {
showApps: true,
hide: false,
}),
};
getInitialState: function() {
return {
constructor(props) {
super(props);
this.state = {
apps: this._getApps(),
};
},
}
componentDidMount: function() {
componentDidMount() {
ScalarMessaging.startListening();
MatrixClientPeg.get().on('RoomState.events', this.onRoomStateEvents);
WidgetEchoStore.on('update', this._updateApps);
this.dispatcherRef = dis.register(this.onAction);
},
}
componentWillUnmount: function() {
componentWillUnmount() {
ScalarMessaging.stopListening();
if (MatrixClientPeg.get()) {
MatrixClientPeg.get().removeListener('RoomState.events', this.onRoomStateEvents);
}
WidgetEchoStore.removeListener('update', this._updateApps);
if (this.dispatcherRef) dis.unregister(this.dispatcherRef);
},
}
// TODO: [REACT-WARNING] Replace with appropriate lifecycle event
// eslint-disable-next-line camelcase
UNSAFE_componentWillReceiveProps(newProps) {
// Room has changed probably, update apps
this._updateApps();
},
}
onAction: function(action) {
onAction = (action) => {
const hideWidgetKey = this.props.room.roomId + '_hide_widget_drawer';
switch (action.action) {
case 'appsDrawer':
@ -98,16 +98,16 @@ export default createReactClass({
break;
}
},
};
onRoomStateEvents: function(ev, state) {
onRoomStateEvents = (ev, state) => {
if (ev.getRoomId() !== this.props.room.roomId || ev.getType() !== 'im.vector.modular.widgets') {
return;
}
this._updateApps();
},
};
_getApps: function() {
_getApps() {
const widgets = WidgetEchoStore.getEchoedRoomWidgets(
this.props.room.roomId, WidgetUtils.getRoomWidgets(this.props.room),
);
@ -116,33 +116,33 @@ export default createReactClass({
ev.getStateKey(), ev.getContent(), ev.getSender(), ev.getRoomId(), ev.getId(),
);
});
},
}
_updateApps: function() {
_updateApps = () => {
const apps = this._getApps();
this.setState({
apps: apps,
});
},
};
_canUserModify: function() {
_canUserModify() {
try {
return WidgetUtils.canUserModifyWidgets(this.props.room.roomId);
} catch (err) {
console.error(err);
return false;
}
},
}
_launchManageIntegrations: function() {
_launchManageIntegrations() {
if (SettingsStore.getValue("feature_many_integration_managers")) {
IntegrationManagers.sharedInstance().openAll();
} else {
IntegrationManagers.sharedInstance().getPrimaryManager().open(this.props.room, 'add_integ');
}
},
}
onClickAddWidget: function(e) {
onClickAddWidget = (e) => {
e.preventDefault();
// Display a warning dialog if the max number of widgets have already been added to the room
const apps = this._getApps();
@ -157,9 +157,9 @@ export default createReactClass({
return;
}
this._launchManageIntegrations();
},
};
render: function() {
render() {
const apps = this.state.apps.map((app, index, arr) => {
const capWhitelist = WidgetUtils.getCapWhitelistForAppTypeInRoomId(app.type, this.props.room.roomId);
@ -230,8 +230,8 @@ export default createReactClass({
{ this._canUserModify() && addWidget }
</div>
);
},
});
}
}
const PersistentVResizer = ({
id,

View file

@ -17,7 +17,7 @@ limitations under the License.
import React, {createRef, KeyboardEvent} from 'react';
import classNames from 'classnames';
import flatMap from 'lodash/flatMap';
import {flatMap} from "lodash";
import {ICompletion, ISelectionRange, IProviderCompletions} from '../../../autocomplete/Autocompleter';
import {Room} from 'matrix-js-sdk/src/models/room';

View file

@ -17,7 +17,6 @@ limitations under the License.
import React from 'react';
import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import {MatrixClientPeg} from "../../../MatrixClientPeg";
import * as sdk from '../../../index';
import dis from "../../../dispatcher/dispatcher";
@ -31,10 +30,8 @@ import AutoHideScrollbar from "../../structures/AutoHideScrollbar";
import CallView from "../voip/CallView";
export default createReactClass({
displayName: 'AuxPanel',
propTypes: {
export default class AuxPanel extends React.Component {
static propTypes = {
// js-sdk room object
room: PropTypes.object.isRequired,
userId: PropTypes.string.isRequired,
@ -58,42 +55,46 @@ export default createReactClass({
// content in a way that is likely to make it change size.
onResize: PropTypes.func,
fullHeight: PropTypes.bool,
},
};
getDefaultProps: () => ({
static defaultProps = {
showApps: true,
hideAppsDrawer: false,
}),
};
getInitialState: function() {
return { counters: this._computeCounters() };
},
constructor(props) {
super(props);
componentDidMount: function() {
this.state = {
counters: this._computeCounters(),
};
}
componentDidMount() {
const cli = MatrixClientPeg.get();
cli.on("RoomState.events", this._rateLimitedUpdate);
},
}
componentWillUnmount: function() {
componentWillUnmount() {
const cli = MatrixClientPeg.get();
if (cli) {
cli.removeListener("RoomState.events", this._rateLimitedUpdate);
}
},
}
shouldComponentUpdate: function(nextProps, nextState) {
shouldComponentUpdate(nextProps, nextState) {
return (!ObjectUtils.shallowEqual(this.props, nextProps) ||
!ObjectUtils.shallowEqual(this.state, nextState));
},
}
componentDidUpdate: function(prevProps, prevState) {
componentDidUpdate(prevProps, prevState) {
// most changes are likely to cause a resize
if (this.props.onResize) {
this.props.onResize();
}
},
}
onConferenceNotificationClick: function(ev, type) {
onConferenceNotificationClick = (ev, type) => {
dis.dispatch({
action: 'place_call',
type: type,
@ -101,15 +102,15 @@ export default createReactClass({
});
ev.stopPropagation();
ev.preventDefault();
},
};
_rateLimitedUpdate: new RateLimitedFunc(function() {
_rateLimitedUpdate = new RateLimitedFunc(() => {
if (SettingsStore.getValue("feature_state_counters")) {
this.setState({counters: this._computeCounters()});
}
}, 500),
}, 500);
_computeCounters: function() {
_computeCounters() {
let counters = [];
if (this.props.room && SettingsStore.getValue("feature_state_counters")) {
@ -140,9 +141,9 @@ export default createReactClass({
}
return counters;
},
}
render: function() {
render() {
const TintableSvg = sdk.getComponent("elements.TintableSvg");
let fileDropTarget = null;
@ -275,5 +276,5 @@ export default createReactClass({
{ this.props.children }
</AutoHideScrollbar>
);
},
});
}
}

View file

@ -18,7 +18,6 @@ limitations under the License.
import React from 'react';
import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import * as sdk from '../../../index';
import AccessibleButton from '../elements/AccessibleButton';
import { _t } from '../../../languageHandler';
@ -51,10 +50,8 @@ function presenceClassForMember(presenceState, lastActiveAgo, showPresence) {
}
}
const EntityTile = createReactClass({
displayName: 'EntityTile',
propTypes: {
class EntityTile extends React.Component {
static propTypes = {
name: PropTypes.string,
title: PropTypes.string,
avatarJsx: PropTypes.any, // <BaseAvatar />
@ -70,33 +67,29 @@ const EntityTile = createReactClass({
showPresence: PropTypes.bool,
subtextLabel: PropTypes.string,
e2eStatus: PropTypes.string,
},
};
getDefaultProps: function() {
return {
shouldComponentUpdate: function(nextProps, nextState) { return true; },
onClick: function() {},
presenceState: "offline",
presenceLastActiveAgo: 0,
presenceLastTs: 0,
showInviteButton: false,
suppressOnHover: false,
showPresence: true,
};
},
static defaultProps = {
shouldComponentUpdate: function(nextProps, nextState) { return true; },
onClick: function() {},
presenceState: "offline",
presenceLastActiveAgo: 0,
presenceLastTs: 0,
showInviteButton: false,
suppressOnHover: false,
showPresence: true,
};
getInitialState: function() {
return {
hover: false,
};
},
state = {
hover: false,
};
shouldComponentUpdate: function(nextProps, nextState) {
shouldComponentUpdate(nextProps, nextState) {
if (this.state.hover !== nextState.hover) return true;
return this.props.shouldComponentUpdate(nextProps, nextState);
},
}
render: function() {
render() {
const mainClassNames = {
"mx_EntityTile": true,
"mx_EntityTile_noHover": this.props.suppressOnHover,
@ -193,8 +186,8 @@ const EntityTile = createReactClass({
</AccessibleButton>
</div>
);
},
});
}
}
EntityTile.POWER_STATUS_MODERATOR = "moderator";
EntityTile.POWER_STATUS_ADMIN = "admin";

View file

@ -20,7 +20,6 @@ limitations under the License.
import ReplyThread from "../elements/ReplyThread";
import React, {createRef} from 'react';
import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import classNames from "classnames";
import { _t, _td } from '../../../languageHandler';
import * as TextForEvent from "../../../TextForEvent";
@ -127,10 +126,8 @@ const MAX_READ_AVATARS = 5;
// | '--------------------------------------' |
// '----------------------------------------------------------'
export default createReactClass({
displayName: 'EventTile',
propTypes: {
export default class EventTile extends React.Component {
static propTypes = {
/* the MatrixEvent to show */
mxEvent: PropTypes.object.isRequired,
@ -209,17 +206,19 @@ export default createReactClass({
// whether to use the irc layout
useIRCLayout: PropTypes.bool,
},
};
getDefaultProps: function() {
return {
// no-op function because onHeightChanged is optional yet some sub-components assume its existence
onHeightChanged: function() {},
};
},
static defaultProps = {
// no-op function because onHeightChanged is optional yet some sub-components assume its existence
onHeightChanged: function() {},
};
getInitialState: function() {
return {
static contextType = MatrixClientContext;
constructor(props, context) {
super(props, context);
this.state = {
// Whether the action bar is focused.
actionBarFocused: false,
// Whether all read receipts are being displayed. If not, only display
@ -232,23 +231,21 @@ export default createReactClass({
// The Relations model from the JS SDK for reactions to `mxEvent`
reactions: this.getReactions(),
};
},
statics: {
contextType: MatrixClientContext,
},
// TODO: [REACT-WARNING] Replace component with real class, use constructor for refs
UNSAFE_componentWillMount: function() {
// don't do RR animations until we are mounted
this._suppressReadReceiptAnimation = true;
this._verifyEvent(this.props.mxEvent);
this._tile = createRef();
this._replyThread = createRef();
},
}
componentDidMount: function() {
// TODO: [REACT-WARNING] Move into constructor
// eslint-disable-next-line camelcase
UNSAFE_componentWillMount() {
this._verifyEvent(this.props.mxEvent);
}
componentDidMount() {
this._suppressReadReceiptAnimation = false;
const client = this.context;
client.on("deviceVerificationChanged", this.onDeviceVerificationChanged);
@ -257,26 +254,27 @@ export default createReactClass({
if (this.props.showReactions) {
this.props.mxEvent.on("Event.relationsCreated", this._onReactionsCreated);
}
},
}
// TODO: [REACT-WARNING] Replace with appropriate lifecycle event
UNSAFE_componentWillReceiveProps: function(nextProps) {
// eslint-disable-next-line camelcase
UNSAFE_componentWillReceiveProps(nextProps) {
// re-check the sender verification as outgoing events progress through
// the send process.
if (nextProps.eventSendStatus !== this.props.eventSendStatus) {
this._verifyEvent(nextProps.mxEvent);
}
},
}
shouldComponentUpdate: function(nextProps, nextState) {
shouldComponentUpdate(nextProps, nextState) {
if (!ObjectUtils.shallowEqual(this.state, nextState)) {
return true;
}
return !this._propsEqual(this.props, nextProps);
},
}
componentWillUnmount: function() {
componentWillUnmount() {
const client = this.context;
client.removeListener("deviceVerificationChanged", this.onDeviceVerificationChanged);
client.removeListener("userTrustStatusChanged", this.onUserVerificationChanged);
@ -284,31 +282,31 @@ export default createReactClass({
if (this.props.showReactions) {
this.props.mxEvent.removeListener("Event.relationsCreated", this._onReactionsCreated);
}
},
}
/** called when the event is decrypted after we show it.
*/
_onDecrypted: function() {
_onDecrypted = () => {
// we need to re-verify the sending device.
// (we call onHeightChanged in _verifyEvent to handle the case where decryption
// has caused a change in size of the event tile)
this._verifyEvent(this.props.mxEvent);
this.forceUpdate();
},
};
onDeviceVerificationChanged: function(userId, device) {
onDeviceVerificationChanged = (userId, device) => {
if (userId === this.props.mxEvent.getSender()) {
this._verifyEvent(this.props.mxEvent);
}
},
};
onUserVerificationChanged: function(userId, _trustStatus) {
onUserVerificationChanged = (userId, _trustStatus) => {
if (userId === this.props.mxEvent.getSender()) {
this._verifyEvent(this.props.mxEvent);
}
},
};
_verifyEvent: async function(mxEvent) {
async _verifyEvent(mxEvent) {
if (!mxEvent.isEncrypted()) {
return;
}
@ -360,9 +358,9 @@ export default createReactClass({
this.setState({
verified: E2E_STATE.VERIFIED,
}, this.props.onHeightChanged); // Decryption may have caused a change in size
},
}
_propsEqual: function(objA, objB) {
_propsEqual(objA, objB) {
const keysA = Object.keys(objA);
const keysB = Object.keys(objB);
@ -408,9 +406,9 @@ export default createReactClass({
}
}
return true;
},
}
shouldHighlight: function() {
shouldHighlight() {
const actions = this.context.getPushActionsForEvent(this.props.mxEvent.replacingEvent() || this.props.mxEvent);
if (!actions || !actions.tweaks) { return false; }
@ -420,15 +418,15 @@ export default createReactClass({
}
return actions.tweaks.highlight;
},
}
toggleAllReadAvatars: function() {
toggleAllReadAvatars = () => {
this.setState({
allReadAvatars: !this.state.allReadAvatars,
});
},
};
getReadAvatars: function() {
getReadAvatars() {
// return early if there are no read receipts
if (!this.props.readReceipts || this.props.readReceipts.length === 0) {
return (<span className="mx_EventTile_readAvatars" />);
@ -494,17 +492,17 @@ export default createReactClass({
{ remText }
{ avatars }
</span>;
},
}
onSenderProfileClick: function(event) {
onSenderProfileClick = event => {
const mxEvent = this.props.mxEvent;
dis.dispatch({
action: 'insert_mention',
user_id: mxEvent.getSender(),
});
},
};
onRequestKeysClick: function() {
onRequestKeysClick = () => {
this.setState({
// Indicate in the UI that the keys have been requested (this is expected to
// be reset if the component is mounted in the future).
@ -515,9 +513,9 @@ export default createReactClass({
// is received for the request with the required keys, the event could be
// decrypted successfully.
this.context.cancelAndResendEventRoomKeyRequest(this.props.mxEvent);
},
};
onPermalinkClicked: function(e) {
onPermalinkClicked = e => {
// This allows the permalink to be opened in a new tab/window or copied as
// matrix.to, but also for it to enable routing within Element when clicked.
e.preventDefault();
@ -527,9 +525,9 @@ export default createReactClass({
highlighted: true,
room_id: this.props.mxEvent.getRoomId(),
});
},
};
_renderE2EPadlock: function() {
_renderE2EPadlock() {
const ev = this.props.mxEvent;
// event could not be decrypted
@ -570,23 +568,19 @@ export default createReactClass({
// no padlock needed
return null;
},
}
onActionBarFocusChange(focused) {
onActionBarFocusChange = focused => {
this.setState({
actionBarFocused: focused,
});
},
};
getTile() {
return this._tile.current;
},
getTile = () => this._tile.current;
getReplyThread() {
return this._replyThread.current;
},
getReplyThread = () => this._replyThread.current;
getReactions() {
getReactions = () => {
if (
!this.props.showReactions ||
!this.props.getRelationsForEvent
@ -602,9 +596,9 @@ export default createReactClass({
console.trace("Stacktrace for https://github.com/vector-im/element-web/issues/11120");
}
return this.props.getRelationsForEvent(eventId, "m.annotation", "m.reaction");
},
};
_onReactionsCreated(relationType, eventType) {
_onReactionsCreated = (relationType, eventType) => {
if (relationType !== "m.annotation" || eventType !== "m.reaction") {
return;
}
@ -612,9 +606,9 @@ export default createReactClass({
this.setState({
reactions: this.getReactions(),
});
},
};
render: function() {
render() {
const MessageTimestamp = sdk.getComponent('messages.MessageTimestamp');
const SenderProfile = sdk.getComponent('messages.SenderProfile');
const MemberAvatar = sdk.getComponent('avatars.MemberAvatar');
@ -947,8 +941,8 @@ export default createReactClass({
);
}
}
},
});
}
}
// XXX this'll eventually be dynamic based on the fields once we have extensible event types
const messageTypes = ['m.room.message', 'm.sticker'];

View file

@ -17,49 +17,46 @@
import React from 'react';
import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import { _t } from '../../../languageHandler';
import dis from '../../../dispatcher/dispatcher';
import {Key} from '../../../Keyboard';
export default createReactClass({
displayName: 'ForwardMessage',
propTypes: {
export default class ForwardMessage extends React.Component {
static propTypes = {
onCancelClick: PropTypes.func.isRequired,
},
};
componentDidMount: function() {
componentDidMount() {
dis.dispatch({
action: 'panel_disable',
middleDisabled: true,
});
document.addEventListener('keydown', this._onKeyDown);
},
}
componentWillUnmount: function() {
componentWillUnmount() {
dis.dispatch({
action: 'panel_disable',
middleDisabled: false,
});
document.removeEventListener('keydown', this._onKeyDown);
},
}
_onKeyDown: function(ev) {
_onKeyDown = ev => {
switch (ev.key) {
case Key.ESCAPE:
this.props.onCancelClick();
break;
}
},
};
render: function() {
render() {
return (
<div className="mx_ForwardMessage">
<h1>{ _t('Please select the destination room for this message') }</h1>
</div>
);
},
});
}
}

View file

@ -17,7 +17,6 @@ limitations under the License.
import React, {createRef} from 'react';
import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import { AllHtmlEntities } from 'html-entities';
import {linkifyElement} from '../../../HtmlUtils';
import SettingsStore from "../../../settings/SettingsStore";
@ -27,24 +26,21 @@ import Modal from "../../../Modal";
import * as ImageUtils from "../../../ImageUtils";
import { _t } from "../../../languageHandler";
export default createReactClass({
displayName: 'LinkPreviewWidget',
propTypes: {
export default class LinkPreviewWidget extends React.Component {
static propTypes = {
link: PropTypes.string.isRequired, // the URL being previewed
mxEvent: PropTypes.object.isRequired, // the Event associated with the preview
onCancelClick: PropTypes.func, // called when the preview's cancel ('hide') button is clicked
onHeightChanged: PropTypes.func, // called when the preview's contents has loaded
},
};
getInitialState: function() {
return {
constructor(props) {
super(props);
this.state = {
preview: null,
};
},
// TODO: [REACT-WARNING] Replace component with real class, use constructor for refs
UNSAFE_componentWillMount: function() {
this.unmounted = false;
MatrixClientPeg.get().getUrlPreview(this.props.link, this.props.mxEvent.getTs()).then((res)=>{
if (this.unmounted) {
@ -59,25 +55,25 @@ export default createReactClass({
});
this._description = createRef();
},
}
componentDidMount: function() {
componentDidMount() {
if (this._description.current) {
linkifyElement(this._description.current);
}
},
}
componentDidUpdate: function() {
componentDidUpdate() {
if (this._description.current) {
linkifyElement(this._description.current);
}
},
}
componentWillUnmount: function() {
componentWillUnmount() {
this.unmounted = true;
},
}
onImageClick: function(ev) {
onImageClick = ev => {
const p = this.state.preview;
if (ev.button != 0 || ev.metaKey) return;
ev.preventDefault();
@ -98,9 +94,9 @@ export default createReactClass({
};
Modal.createDialog(ImageView, params, "mx_Dialog_lightbox");
},
};
render: function() {
render() {
const p = this.state.preview;
if (!p || Object.keys(p).length === 0) {
return <div />;
@ -149,5 +145,5 @@ export default createReactClass({
</AccessibleButton>
</div>
);
},
});
}
}

View file

@ -17,7 +17,6 @@ limitations under the License.
*/
import React from 'react';
import createReactClass from 'create-react-class';
import { _t } from '../../../languageHandler';
import SdkConfig from '../../../SdkConfig';
import dis from '../../../dispatcher/dispatcher';
@ -27,6 +26,7 @@ import rate_limited_func from "../../../ratelimitedfunc";
import {MatrixClientPeg} from "../../../MatrixClientPeg";
import * as sdk from "../../../index";
import CallHandler from "../../../CallHandler";
import {CommunityPrototypeStore} from "../../../stores/CommunityPrototypeStore";
const INITIAL_LOAD_NUM_MEMBERS = 30;
const INITIAL_LOAD_NUM_INVITED = 5;
@ -36,29 +36,18 @@ const SHOW_MORE_INCREMENT = 100;
// matches all ASCII punctuation: !"#$%&'()*+,-./:;<=>?@[\]^_`{|}~
const SORT_REGEX = /[\x21-\x2F\x3A-\x40\x5B-\x60\x7B-\x7E]+/g;
export default createReactClass({
displayName: 'MemberList',
export default class MemberList extends React.Component {
constructor(props) {
super(props);
getInitialState: function() {
const cli = MatrixClientPeg.get();
if (cli.hasLazyLoadMembersEnabled()) {
// show an empty list
return this._getMembersState([]);
this.state = this._getMembersState([]);
} else {
return this._getMembersState(this.roomMembers());
this.state = this._getMembersState(this.roomMembers());
}
},
// TODO: [REACT-WARNING] Move this to constructor
UNSAFE_componentWillMount: function() {
this._mounted = true;
const cli = MatrixClientPeg.get();
if (cli.hasLazyLoadMembersEnabled()) {
this._showMembersAccordingToMembershipWithLL();
cli.on("Room.myMembership", this.onMyMembership);
} else {
this._listenForMembersChanges();
}
cli.on("Room", this.onRoom); // invites & joining after peek
const enablePresenceByHsUrl = SdkConfig.get()["enable_presence_by_hs_url"];
const hsUrl = MatrixClientPeg.get().baseUrl;
@ -66,9 +55,21 @@ export default createReactClass({
if (enablePresenceByHsUrl && enablePresenceByHsUrl[hsUrl] !== undefined) {
this._showPresence = enablePresenceByHsUrl[hsUrl];
}
},
}
_listenForMembersChanges: function() {
// eslint-disable-next-line camelcase
UNSAFE_componentWillMount() {
const cli = MatrixClientPeg.get();
this._mounted = true;
if (cli.hasLazyLoadMembersEnabled()) {
this._showMembersAccordingToMembershipWithLL();
cli.on("Room.myMembership", this.onMyMembership);
} else {
this._listenForMembersChanges();
}
}
_listenForMembersChanges() {
const cli = MatrixClientPeg.get();
cli.on("RoomState.members", this.onRoomStateMember);
cli.on("RoomMember.name", this.onRoomMemberName);
@ -80,9 +81,9 @@ export default createReactClass({
cli.on("User.presence", this.onUserPresenceChange);
cli.on("User.currentlyActive", this.onUserPresenceChange);
// cli.on("Room.timeline", this.onRoomTimeline);
},
}
componentWillUnmount: function() {
componentWillUnmount() {
this._mounted = false;
const cli = MatrixClientPeg.get();
if (cli) {
@ -98,14 +99,14 @@ export default createReactClass({
// cancel any pending calls to the rate_limited_funcs
this._updateList.cancelPendingCall();
},
}
/**
* If lazy loading is enabled, either:
* show a spinner and load the members if the user is joined,
* or show the members available so far if the user is invited
*/
_showMembersAccordingToMembershipWithLL: async function() {
async _showMembersAccordingToMembershipWithLL() {
const cli = MatrixClientPeg.get();
if (cli.hasLazyLoadMembersEnabled()) {
const cli = MatrixClientPeg.get();
@ -125,9 +126,9 @@ export default createReactClass({
this.setState(this._getMembersState(this.roomMembers()));
}
}
},
}
_getMembersState: function(members) {
_getMembersState(members) {
// set the state after determining _showPresence to make sure it's
// taken into account while rerendering
return {
@ -142,9 +143,9 @@ export default createReactClass({
truncateAtInvited: INITIAL_LOAD_NUM_INVITED,
searchQuery: "",
};
},
}
onUserPresenceChange(event, user) {
onUserPresenceChange = (event, user) => {
// Attach a SINGLE listener for global presence changes then locate the
// member tile and re-render it. This is more efficient than every tile
// ever attaching their own listener.
@ -153,9 +154,9 @@ export default createReactClass({
if (tile) {
this._updateList(); // reorder the membership list
}
},
};
onRoom: function(room) {
onRoom = room => {
if (room.roomId !== this.props.roomId) {
return;
}
@ -163,40 +164,40 @@ export default createReactClass({
// we need to wait till the room is fully populated with state
// before refreshing the member list else we get a stale list.
this._showMembersAccordingToMembershipWithLL();
},
};
onMyMembership: function(room, membership, oldMembership) {
onMyMembership = (room, membership, oldMembership) => {
if (room.roomId === this.props.roomId && membership === "join") {
this._showMembersAccordingToMembershipWithLL();
}
},
};
onRoomStateMember: function(ev, state, member) {
onRoomStateMember = (ev, state, member) => {
if (member.roomId !== this.props.roomId) {
return;
}
this._updateList();
},
};
onRoomMemberName: function(ev, member) {
onRoomMemberName = (ev, member) => {
if (member.roomId !== this.props.roomId) {
return;
}
this._updateList();
},
};
onRoomStateEvent: function(event, state) {
onRoomStateEvent = (event, state) => {
if (event.getRoomId() === this.props.roomId &&
event.getType() === "m.room.third_party_invite") {
this._updateList();
}
},
};
_updateList: rate_limited_func(function() {
_updateList = rate_limited_func(() => {
this._updateListNow();
}, 500),
}, 500);
_updateListNow: function() {
_updateListNow() {
// console.log("Updating memberlist");
const newState = {
loading: false,
@ -205,9 +206,9 @@ export default createReactClass({
newState.filteredJoinedMembers = this._filterMembers(newState.members, 'join', this.state.searchQuery);
newState.filteredInvitedMembers = this._filterMembers(newState.members, 'invite', this.state.searchQuery);
this.setState(newState);
},
}
getMembersWithUser: function() {
getMembersWithUser() {
if (!this.props.roomId) return [];
const cli = MatrixClientPeg.get();
const room = cli.getRoom(this.props.roomId);
@ -228,9 +229,9 @@ export default createReactClass({
});
return allMembers;
},
}
roomMembers: function() {
roomMembers() {
const ConferenceHandler = CallHandler.getConferenceHandler();
const allMembers = this.getMembersWithUser();
@ -244,17 +245,17 @@ export default createReactClass({
});
filteredAndSortedMembers.sort(this.memberSort);
return filteredAndSortedMembers;
},
}
_createOverflowTileJoined: function(overflowCount, totalCount) {
_createOverflowTileJoined = (overflowCount, totalCount) => {
return this._createOverflowTile(overflowCount, totalCount, this._showMoreJoinedMemberList);
},
};
_createOverflowTileInvited: function(overflowCount, totalCount) {
_createOverflowTileInvited = (overflowCount, totalCount) => {
return this._createOverflowTile(overflowCount, totalCount, this._showMoreInvitedMemberList);
},
};
_createOverflowTile: function(overflowCount, totalCount, onClick) {
_createOverflowTile = (overflowCount, totalCount, onClick) => {
// For now we'll pretend this is any entity. It should probably be a separate tile.
const EntityTile = sdk.getComponent("rooms.EntityTile");
const BaseAvatar = sdk.getComponent("avatars.BaseAvatar");
@ -265,33 +266,33 @@ export default createReactClass({
} name={text} presenceState="online" suppressOnHover={true}
onClick={onClick} />
);
},
};
_showMoreJoinedMemberList: function() {
_showMoreJoinedMemberList = () => {
this.setState({
truncateAtJoined: this.state.truncateAtJoined + SHOW_MORE_INCREMENT,
});
},
};
_showMoreInvitedMemberList: function() {
_showMoreInvitedMemberList = () => {
this.setState({
truncateAtInvited: this.state.truncateAtInvited + SHOW_MORE_INCREMENT,
});
},
};
memberString: function(member) {
memberString(member) {
if (!member) {
return "(null)";
} else {
const u = member.user;
return "(" + member.name + ", " + member.powerLevel + ", " + (u ? u.lastActiveAgo : "<null>") + ", " + (u ? u.getLastActiveTs() : "<null>") + ", " + (u ? u.currentlyActive : "<null>") + ", " + (u ? u.presence : "<null>") + ")";
}
},
}
// returns negative if a comes before b,
// returns 0 if a and b are equivalent in ordering
// returns positive if a comes after b.
memberSort: function(memberA, memberB) {
memberSort = (memberA, memberB) => {
// order by presence, with "active now" first.
// ...and then by power level
// ...and then by last active
@ -348,24 +349,24 @@ export default createReactClass({
ignorePunctuation: true,
sensitivity: "base",
});
},
};
onSearchQueryChanged: function(searchQuery) {
onSearchQueryChanged = searchQuery => {
this.setState({
searchQuery,
filteredJoinedMembers: this._filterMembers(this.state.members, 'join', searchQuery),
filteredInvitedMembers: this._filterMembers(this.state.members, 'invite', searchQuery),
});
},
};
_onPending3pidInviteClick: function(inviteEvent) {
_onPending3pidInviteClick = inviteEvent => {
dis.dispatch({
action: 'view_3pid_invite',
event: inviteEvent,
});
},
};
_filterMembers: function(members, membership, query) {
_filterMembers(members, membership, query) {
return members.filter((m) => {
if (query) {
query = query.toLowerCase();
@ -379,9 +380,9 @@ export default createReactClass({
return m.membership === membership;
});
},
}
_getPending3PidInvites: function() {
_getPending3PidInvites() {
// include 3pid invites (m.room.third_party_invite) state events.
// The HS may have already converted these into m.room.member invites so
// we shouldn't add them if the 3pid invite state key (token) is in the
@ -399,9 +400,9 @@ export default createReactClass({
return true;
});
}
},
}
_makeMemberTiles: function(members) {
_makeMemberTiles(members) {
const MemberTile = sdk.getComponent("rooms.MemberTile");
const EntityTile = sdk.getComponent("rooms.EntityTile");
@ -415,30 +416,26 @@ export default createReactClass({
onClick={() => this._onPending3pidInviteClick(m)} />;
}
});
},
}
_getChildrenJoined: function(start, end) {
return this._makeMemberTiles(this.state.filteredJoinedMembers.slice(start, end));
},
_getChildrenJoined = (start, end) => this._makeMemberTiles(this.state.filteredJoinedMembers.slice(start, end));
_getChildCountJoined: function() {
return this.state.filteredJoinedMembers.length;
},
_getChildCountJoined = () => this.state.filteredJoinedMembers.length;
_getChildrenInvited: function(start, end) {
_getChildrenInvited = (start, end) => {
let targets = this.state.filteredInvitedMembers;
if (end > this.state.filteredInvitedMembers.length) {
targets = targets.concat(this._getPending3PidInvites());
}
return this._makeMemberTiles(targets.slice(start, end));
},
};
_getChildCountInvited: function() {
_getChildCountInvited = () => {
return this.state.filteredInvitedMembers.length + (this._getPending3PidInvites() || []).length;
},
}
render: function() {
render() {
if (this.state.loading) {
const Spinner = sdk.getComponent("elements.Spinner");
return <div className="mx_MemberList"><Spinner /></div>;
@ -464,10 +461,16 @@ export default createReactClass({
}
}
let inviteButtonText = _t("Invite to this room");
const chat = CommunityPrototypeStore.instance.getSelectedCommunityGeneralChat();
if (chat && chat.roomId === this.props.roomId) {
inviteButtonText = _t("Invite to this community");
}
const AccessibleButton = sdk.getComponent("elements.AccessibleButton");
inviteButton =
<AccessibleButton className="mx_MemberList_invite" onClick={this.onInviteButtonClick} disabled={!canInvite}>
<span>{ _t('Invite to this room') }</span>
<span>{ inviteButtonText }</span>
</AccessibleButton>;
}
@ -501,9 +504,9 @@ export default createReactClass({
onSearch={ this.onSearchQueryChanged } />
</div>
);
},
}
onInviteButtonClick: function() {
onInviteButtonClick = () => {
if (MatrixClientPeg.get().isGuest()) {
dis.dispatch({action: 'require_registration'});
return;
@ -514,5 +517,5 @@ export default createReactClass({
action: 'view_invite',
roomId: this.props.roomId,
});
},
});
};
}

View file

@ -18,34 +18,31 @@ limitations under the License.
import SettingsStore from "../../../settings/SettingsStore";
import React from 'react';
import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import * as sdk from "../../../index";
import dis from "../../../dispatcher/dispatcher";
import { _t } from '../../../languageHandler';
import { MatrixClientPeg } from "../../../MatrixClientPeg";
import {Action} from "../../../dispatcher/actions";
export default createReactClass({
displayName: 'MemberTile',
propTypes: {
export default class MemberTile extends React.Component {
static propTypes = {
member: PropTypes.any.isRequired, // RoomMember
showPresence: PropTypes.bool,
},
};
getDefaultProps: function() {
return {
showPresence: true,
};
},
static defaultProps = {
showPresence: true,
};
getInitialState: function() {
return {
constructor(props) {
super(props);
this.state = {
statusMessage: this.getStatusMessage(),
isRoomEncrypted: false,
e2eStatus: null,
};
},
}
componentDidMount() {
const cli = MatrixClientPeg.get();
@ -72,7 +69,7 @@ export default createReactClass({
cli.on("RoomState.events", this.onRoomStateEvents);
}
}
},
}
componentWillUnmount() {
const cli = MatrixClientPeg.get();
@ -90,9 +87,9 @@ export default createReactClass({
cli.removeListener("userTrustStatusChanged", this.onUserTrustStatusChanged);
cli.removeListener("deviceVerificationChanged", this.onDeviceVerificationChanged);
}
},
}
onRoomStateEvents: function(ev) {
onRoomStateEvents = ev => {
if (ev.getType() !== "m.room.encryption") return;
const { roomId } = this.props.member;
if (ev.getRoomId() !== roomId) return;
@ -104,19 +101,19 @@ export default createReactClass({
isRoomEncrypted: true,
});
this.updateE2EStatus();
},
};
onUserTrustStatusChanged: function(userId, trustStatus) {
onUserTrustStatusChanged = (userId, trustStatus) => {
if (userId !== this.props.member.userId) return;
this.updateE2EStatus();
},
};
onDeviceVerificationChanged: function(userId, deviceId, deviceInfo) {
onDeviceVerificationChanged = (userId, deviceId, deviceInfo) => {
if (userId !== this.props.member.userId) return;
this.updateE2EStatus();
},
};
updateE2EStatus: async function() {
async updateE2EStatus() {
const cli = MatrixClientPeg.get();
const { userId } = this.props.member;
const isMe = userId === cli.getUserId();
@ -142,7 +139,7 @@ export default createReactClass({
this.setState({
e2eStatus: anyDeviceUnverified ? "warning" : "verified",
});
},
}
getStatusMessage() {
const { user } = this.props.member;
@ -150,16 +147,16 @@ export default createReactClass({
return "";
}
return user._unstable_statusMessage;
},
}
_onStatusMessageCommitted() {
_onStatusMessageCommitted = () => {
// The `User` object has observed a status message change.
this.setState({
statusMessage: this.getStatusMessage(),
});
},
};
shouldComponentUpdate: function(nextProps, nextState) {
shouldComponentUpdate(nextProps, nextState) {
if (
this.member_last_modified_time === undefined ||
this.member_last_modified_time < nextProps.member.getLastModifiedTime()
@ -180,27 +177,27 @@ export default createReactClass({
return true;
}
return false;
},
}
onClick: function(e) {
onClick = e => {
dis.dispatch({
action: Action.ViewUser,
member: this.props.member,
});
},
};
_getDisplayName: function() {
_getDisplayName() {
return this.props.member.name;
},
}
getPowerLabel: function() {
getPowerLabel() {
return _t("%(userName)s (power %(powerLevelNumber)s)", {
userName: this.props.member.userId,
powerLevelNumber: this.props.member.powerLevel,
});
},
}
render: function() {
render() {
const MemberAvatar = sdk.getComponent('avatars.MemberAvatar');
const EntityTile = sdk.getComponent('rooms.EntityTile');
@ -260,5 +257,5 @@ export default createReactClass({
onClick={this.onClick}
/>
);
},
});
}
}

View file

@ -16,7 +16,6 @@ limitations under the License.
import React from "react";
import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import {MatrixClientPeg} from "../../../MatrixClientPeg";
import dis from "../../../dispatcher/dispatcher";
import AccessibleButton from "../elements/AccessibleButton";
@ -25,22 +24,23 @@ import MemberAvatar from "../avatars/MemberAvatar";
import { _t } from '../../../languageHandler';
import {formatFullDate} from '../../../DateUtils';
export default createReactClass({
displayName: 'PinnedEventTile',
propTypes: {
export default class PinnedEventTile extends React.Component {
static propTypes = {
mxRoom: PropTypes.object.isRequired,
mxEvent: PropTypes.object.isRequired,
onUnpinned: PropTypes.func,
},
onTileClicked: function() {
};
onTileClicked = () => {
dis.dispatch({
action: 'view_room',
event_id: this.props.mxEvent.getId(),
highlighted: true,
room_id: this.props.mxEvent.getRoomId(),
});
},
onUnpinClicked: function() {
};
onUnpinClicked = () => {
const pinnedEvents = this.props.mxRoom.currentState.getStateEvents("m.room.pinned_events", "");
if (!pinnedEvents || !pinnedEvents.getContent().pinned) {
// Nothing to do: already unpinned
@ -56,11 +56,13 @@ export default createReactClass({
});
} else if (this.props.onUnpinned) this.props.onUnpinned();
}
},
_canUnpin: function() {
};
_canUnpin() {
return this.props.mxRoom.currentState.mayClientSendStateEvent('m.room.pinned_events', MatrixClientPeg.get());
},
render: function() {
}
render() {
const sender = this.props.mxEvent.getSender();
// Get the latest sender profile rather than historical
const senderProfile = this.props.mxRoom.getMember(sender);
@ -100,5 +102,5 @@ export default createReactClass({
</div>
</div>
);
},
});
}
}

View file

@ -17,46 +17,42 @@ limitations under the License.
import React from "react";
import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import {MatrixClientPeg} from "../../../MatrixClientPeg";
import AccessibleButton from "../elements/AccessibleButton";
import PinnedEventTile from "./PinnedEventTile";
import { _t } from '../../../languageHandler';
import PinningUtils from "../../../utils/PinningUtils";
export default createReactClass({
displayName: 'PinnedEventsPanel',
propTypes: {
export default class PinnedEventsPanel extends React.Component {
static propTypes = {
// The Room from the js-sdk we're going to show pinned events for
room: PropTypes.object.isRequired,
onCancelClick: PropTypes.func,
},
};
getInitialState: function() {
return {
loading: true,
};
},
state = {
loading: true,
};
componentDidMount: function() {
componentDidMount() {
this._updatePinnedMessages();
MatrixClientPeg.get().on("RoomState.events", this._onStateEvent);
},
}
componentWillUnmount: function() {
componentWillUnmount() {
if (MatrixClientPeg.get()) {
MatrixClientPeg.get().removeListener("RoomState.events", this._onStateEvent);
}
},
}
_onStateEvent: function(ev) {
_onStateEvent = ev => {
if (ev.getRoomId() === this.props.room.roomId && ev.getType() === "m.room.pinned_events") {
this._updatePinnedMessages();
}
},
};
_updatePinnedMessages: function() {
_updatePinnedMessages = () => {
const pinnedEvents = this.props.room.currentState.getStateEvents("m.room.pinned_events", "");
if (!pinnedEvents || !pinnedEvents.getContent().pinned) {
this.setState({ loading: false, pinned: [] });
@ -85,9 +81,9 @@ export default createReactClass({
}
this._updateReadState();
},
};
_updateReadState: function() {
_updateReadState() {
const pinnedEvents = this.props.room.currentState.getStateEvents("m.room.pinned_events", "");
if (!pinnedEvents) return; // nothing to read
@ -107,9 +103,9 @@ export default createReactClass({
event_ids: readStateEvents,
});
}
},
}
_getPinnedTiles: function() {
_getPinnedTiles() {
if (this.state.pinned.length === 0) {
return (<div>{ _t("No pinned messages.") }</div>);
}
@ -120,9 +116,9 @@ export default createReactClass({
mxEvent={context.event}
onUnpinned={this._updatePinnedMessages} />);
});
},
}
render: function() {
render() {
let tiles = <div>{ _t("Loading...") }</div>;
if (this.state && !this.state.loading) {
tiles = this._getPinnedTiles();
@ -139,5 +135,5 @@ export default createReactClass({
</div>
</div>
);
},
});
}
}

View file

@ -16,15 +16,12 @@ limitations under the License.
import React from 'react';
import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import { _t } from '../../../languageHandler';
export default createReactClass({
displayName: 'PresenceLabel',
propTypes: {
export default class PresenceLabel extends React.Component {
static propTypes = {
// number of milliseconds ago this user was last active.
// zero = unknown
activeAgo: PropTypes.number,
@ -35,18 +32,16 @@ export default createReactClass({
// offline, online, etc
presenceState: PropTypes.string,
},
};
getDefaultProps: function() {
return {
ago: -1,
presenceState: null,
};
},
static defaultProps = {
activeAgo: -1,
presenceState: null,
};
// Return duration as a string using appropriate time units
// XXX: This would be better handled using a culture-aware library, but we don't use one yet.
getDuration: function(time) {
getDuration(time) {
if (!time) return;
const t = parseInt(time / 1000);
const s = t % 60;
@ -66,9 +61,9 @@ export default createReactClass({
return _t("%(duration)sh", {duration: h});
}
return _t("%(duration)sd", {duration: d});
},
}
getPrettyPresence: function(presence, activeAgo, currentlyActive) {
getPrettyPresence(presence, activeAgo, currentlyActive) {
if (!currentlyActive && activeAgo !== undefined && activeAgo > 0) {
const duration = this.getDuration(activeAgo);
if (presence === "online") return _t("Online for %(duration)s", { duration: duration });
@ -81,13 +76,13 @@ export default createReactClass({
if (presence === "offline") return _t("Offline");
return _t("Unknown");
}
},
}
render: function() {
render() {
return (
<div className="mx_PresenceLabel">
{ this.getPrettyPresence(this.props.presenceState, this.props.activeAgo, this.props.currentlyActive) }
</div>
);
},
});
}
}

View file

@ -17,7 +17,6 @@ limitations under the License.
import React, {createRef} from 'react';
import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import '../../../VelocityBounce';
import { _t } from '../../../languageHandler';
import {formatDate} from '../../../DateUtils';
@ -33,10 +32,8 @@ try {
} catch (e) {
}
export default createReactClass({
displayName: 'ReadReceiptMarker',
propTypes: {
export default class ReadReceiptMarker extends React.Component {
static propTypes = {
// the RoomMember to show the RR for
member: PropTypes.object,
// userId to fallback the avatar to
@ -70,30 +67,27 @@ export default createReactClass({
// True to show twelve hour format, false otherwise
showTwelveHour: PropTypes.bool,
},
};
getDefaultProps: function() {
return {
leftOffset: 0,
};
},
static defaultProps = {
leftOffset: 0,
};
getInitialState: function() {
// if we are going to animate the RR, we don't show it on first render,
// and instead just add a placeholder to the DOM; once we've been
// mounted, we start an animation which moves the RR from its old
// position.
return {
constructor(props) {
super(props);
this._avatar = createRef();
this.state = {
// if we are going to animate the RR, we don't show it on first render,
// and instead just add a placeholder to the DOM; once we've been
// mounted, we start an animation which moves the RR from its old
// position.
suppressDisplay: !this.props.suppressAnimation,
};
},
}
// TODO: [REACT-WARNING] Replace component with real class, use constructor for refs
UNSAFE_componentWillMount: function() {
this._avatar = createRef();
},
componentWillUnmount: function() {
componentWillUnmount() {
// before we remove the rr, store its location in the map, so that if
// it reappears, it can be animated from the right place.
const rrInfo = this.props.readReceiptInfo;
@ -112,9 +106,9 @@ export default createReactClass({
rrInfo.top = avatarNode.offsetTop;
rrInfo.left = avatarNode.offsetLeft;
rrInfo.parent = avatarNode.offsetParent;
},
}
componentDidMount: function() {
componentDidMount() {
if (!this.state.suppressDisplay) {
// we've already done our display - nothing more to do.
return;
@ -172,10 +166,9 @@ export default createReactClass({
startStyles: startStyles,
enterTransitionOpts: enterTransitionOpts,
});
},
}
render: function() {
render() {
const MemberAvatar = sdk.getComponent('avatars.MemberAvatar');
if (this.state.suppressDisplay) {
return <div ref={this._avatar} />;
@ -222,5 +215,5 @@ export default createReactClass({
/>
</Velociraptor>
);
},
});
}
}

View file

@ -19,35 +19,32 @@ import dis from '../../../dispatcher/dispatcher';
import React from 'react';
import { _t } from '../../../languageHandler';
import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import classNames from 'classnames';
import {roomShape} from './RoomDetailRow';
export default createReactClass({
displayName: 'RoomDetailList',
propTypes: {
export default class RoomDetailList extends React.Component {
static propTypes = {
rooms: PropTypes.arrayOf(roomShape),
className: PropTypes.string,
},
};
getRows: function() {
getRows() {
if (!this.props.rooms) return [];
const RoomDetailRow = sdk.getComponent('rooms.RoomDetailRow');
return this.props.rooms.map((room, index) => {
return <RoomDetailRow key={index} room={room} onClick={this.onDetailsClick} />;
});
},
}
onDetailsClick: function(ev, room) {
onDetailsClick = (ev, room) => {
dis.dispatch({
action: 'view_room',
room_id: room.roomId,
room_alias: room.canonicalAlias || (room.aliases || [])[0],
});
},
};
render() {
const rows = this.getRows();
@ -64,5 +61,5 @@ export default createReactClass({
return <div className={classNames("mx_RoomDetailList", this.props.className)}>
{ rooms }
</div>;
},
});
}
}

View file

@ -20,7 +20,6 @@ import { _t } from '../../../languageHandler';
import { linkifyElement } from '../../../HtmlUtils';
import {MatrixClientPeg} from '../../../MatrixClientPeg';
import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import {getHttpUriForMxc} from "matrix-js-sdk/src/content-repo";
export function getDisplayAliasForRoom(room) {
@ -40,47 +39,48 @@ export const roomShape = PropTypes.shape({
guestCanJoin: PropTypes.bool,
});
export default createReactClass({
propTypes: {
export default class RoomDetailRow extends React.Component {
static propTypes = {
room: roomShape,
// passes ev, room as args
onClick: PropTypes.func,
onMouseDown: PropTypes.func,
},
};
_linkifyTopic: function() {
constructor(props) {
super(props);
this._topic = createRef();
}
componentDidMount() {
this._linkifyTopic();
}
componentDidUpdate() {
this._linkifyTopic();
}
_linkifyTopic() {
if (this._topic.current) {
linkifyElement(this._topic.current);
}
},
}
// TODO: [REACT-WARNING] Replace component with real class, use constructor for refs
UNSAFE_componentWillMount: function() {
this._topic = createRef();
},
componentDidMount: function() {
this._linkifyTopic();
},
componentDidUpdate: function() {
this._linkifyTopic();
},
onClick: function(ev) {
onClick = (ev) => {
ev.preventDefault();
if (this.props.onClick) {
this.props.onClick(ev, this.props.room);
}
},
};
onTopicClick: function(ev) {
onTopicClick = (ev) => {
// When clicking a link in the topic, prevent the event being propagated
// to `onClick`.
ev.stopPropagation();
},
};
render: function() {
render() {
const BaseAvatar = sdk.getComponent('avatars.BaseAvatar');
const room = this.props.room;
@ -118,5 +118,5 @@ export default createReactClass({
{ room.numJoinedMembers }
</td>
</tr>;
},
});
}
}

View file

@ -17,7 +17,6 @@ limitations under the License.
import React, {createRef} from 'react';
import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import classNames from 'classnames';
import * as sdk from '../../../index';
import { _t } from '../../../languageHandler';
@ -35,10 +34,8 @@ import DecoratedRoomAvatar from "../avatars/DecoratedRoomAvatar";
import {DefaultTagID} from "../../../stores/room-list/models";
import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";
export default createReactClass({
displayName: 'RoomHeader',
propTypes: {
export default class RoomHeader extends React.Component {
static propTypes = {
room: PropTypes.object,
oobData: PropTypes.object,
inRoom: PropTypes.bool,
@ -48,22 +45,21 @@ export default createReactClass({
onLeaveClick: PropTypes.func,
onCancelClick: PropTypes.func,
e2eStatus: PropTypes.string,
},
};
getDefaultProps: function() {
return {
editing: false,
inRoom: false,
onCancelClick: null,
};
},
static defaultProps = {
editing: false,
inRoom: false,
onCancelClick: null,
};
constructor(props) {
super(props);
// TODO: [REACT-WARNING] Replace component with real class, use constructor for refs
UNSAFE_componentWillMount: function() {
this._topic = createRef();
},
}
componentDidMount: function() {
componentDidMount() {
const cli = MatrixClientPeg.get();
cli.on("RoomState.events", this._onRoomStateEvents);
cli.on("Room.accountData", this._onRoomAccountData);
@ -74,15 +70,15 @@ export default createReactClass({
if (this.props.room) {
this.props.room.on("Room.name", this._onRoomNameChange);
}
},
}
componentDidUpdate: function() {
componentDidUpdate() {
if (this._topic.current) {
linkifyElement(this._topic.current);
}
},
}
componentWillUnmount: function() {
componentWillUnmount() {
if (this.props.room) {
this.props.room.removeListener("Room.name", this._onRoomNameChange);
}
@ -91,41 +87,41 @@ export default createReactClass({
cli.removeListener("RoomState.events", this._onRoomStateEvents);
cli.removeListener("Room.accountData", this._onRoomAccountData);
}
},
}
_onRoomStateEvents: function(event, state) {
_onRoomStateEvents = (event, state) => {
if (!this.props.room || event.getRoomId() !== this.props.room.roomId) {
return;
}
// redisplay the room name, topic, etc.
this._rateLimitedUpdate();
},
};
_onRoomAccountData: function(event, room) {
_onRoomAccountData = (event, room) => {
if (!this.props.room || room.roomId !== this.props.room.roomId) return;
if (event.getType() !== "im.vector.room.read_pins") return;
this._rateLimitedUpdate();
},
};
_rateLimitedUpdate: new RateLimitedFunc(function() {
_rateLimitedUpdate = new RateLimitedFunc(function() {
/* eslint-disable babel/no-invalid-this */
this.forceUpdate();
}, 500),
}, 500);
_onRoomNameChange: function(room) {
_onRoomNameChange = (room) => {
this.forceUpdate();
},
};
onShareRoomClick: function(ev) {
onShareRoomClick = (ev) => {
const ShareDialog = sdk.getComponent("dialogs.ShareDialog");
Modal.createTrackedDialog('share room dialog', '', ShareDialog, {
target: this.props.room,
});
},
};
_hasUnreadPins: function() {
_hasUnreadPins() {
const currentPinEvent = this.props.room.currentState.getStateEvents("m.room.pinned_events", '');
if (!currentPinEvent) return false;
if (currentPinEvent.getContent().pinned && currentPinEvent.getContent().pinned.length <= 0) {
@ -142,16 +138,16 @@ export default createReactClass({
// There's pins, and we haven't read any of them
return true;
},
}
_hasPins: function() {
_hasPins() {
const currentPinEvent = this.props.room.currentState.getStateEvents("m.room.pinned_events", '');
if (!currentPinEvent) return false;
return !(currentPinEvent.getContent().pinned && currentPinEvent.getContent().pinned.length <= 0);
},
}
render: function() {
render() {
let searchStatus = null;
let cancelButton = null;
let settingsButton = null;
@ -301,5 +297,5 @@ export default createReactClass({
</div>
</div>
);
},
});
}
}

View file

@ -45,7 +45,7 @@ import { arrayFastClone, arrayHasDiff } from "../../../utils/arrays";
import { objectShallowClone, objectWithOnly } from "../../../utils/objects";
import { IconizedContextMenuOption, IconizedContextMenuOptionList } from "../context_menus/IconizedContextMenu";
import AccessibleButton from "../elements/AccessibleButton";
import TagOrderStore from "../../../stores/TagOrderStore";
import { CommunityPrototypeStore } from "../../../stores/CommunityPrototypeStore";
interface IProps {
onKeyDown: (ev: React.KeyboardEvent) => void;
@ -130,7 +130,7 @@ const TAG_AESTHETICS: {
}}
/>
<IconizedContextMenuOption
label={TagOrderStore.getSelectedPrototypeTag()
label={CommunityPrototypeStore.instance.getSelectedCommunityId()
? _t("Explore community rooms")
: _t("Explore public rooms")}
iconClassName="mx_RoomList_iconExplore"

View file

@ -18,7 +18,6 @@ limitations under the License.
import React from 'react';
import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import * as sdk from '../../../index';
import {MatrixClientPeg} from '../../../MatrixClientPeg';
import dis from '../../../dispatcher/dispatcher';
@ -46,10 +45,8 @@ const MessageCase = Object.freeze({
OtherError: "OtherError",
});
export default createReactClass({
displayName: 'RoomPreviewBar',
propTypes: {
export default class RoomPreviewBar extends React.Component {
static propTypes = {
onJoinClick: PropTypes.func,
onRejectClick: PropTypes.func,
onRejectAndIgnoreClick: PropTypes.func,
@ -86,36 +83,32 @@ export default createReactClass({
// If given, this will be how the room is referred to (eg.
// in error messages).
roomAlias: PropTypes.string,
},
};
getDefaultProps: function() {
return {
onJoinClick: function() {},
};
},
static defaultProps = {
onJoinClick() {},
};
getInitialState: function() {
return {
busy: false,
};
},
state = {
busy: false,
};
componentDidMount: function() {
componentDidMount() {
this._checkInvitedEmail();
CommunityPrototypeStore.instance.on(UPDATE_EVENT, this._onCommunityUpdate);
},
}
componentDidUpdate: function(prevProps, prevState) {
componentDidUpdate(prevProps, prevState) {
if (this.props.invitedEmail !== prevProps.invitedEmail || this.props.inviterName !== prevProps.inviterName) {
this._checkInvitedEmail();
}
},
}
componentWillUnmount: function() {
componentWillUnmount() {
CommunityPrototypeStore.instance.off(UPDATE_EVENT, this._onCommunityUpdate);
},
}
_checkInvitedEmail: async function() {
async _checkInvitedEmail() {
// If this is an invite and we've been told what email address was
// invited, fetch the user's account emails and discovery bindings so we
// can check them against the email that was invited.
@ -148,14 +141,14 @@ export default createReactClass({
}
this.setState({busy: false});
}
},
}
_onCommunityUpdate: function (roomId) {
_onCommunityUpdate = (roomId) => {
if (this.props.room && this.props.room.roomId !== roomId) {
return;
}
this.forceUpdate(); // we have nothing to update
},
};
_getMessageCase() {
const isGuest = MatrixClientPeg.get().isGuest();
@ -207,7 +200,7 @@ export default createReactClass({
} else {
return MessageCase.ViewingRoom;
}
},
}
_getKickOrBanInfo() {
const myMember = this._getMyMember();
@ -221,9 +214,9 @@ export default createReactClass({
kickerMember.name : myMember.events.member.getSender();
const reason = myMember.events.member.getContent().reason;
return {memberName, reason};
},
}
_joinRule: function() {
_joinRule() {
const room = this.props.room;
if (room) {
const joinRules = room.currentState.getStateEvents('m.room.join_rules', '');
@ -231,14 +224,14 @@ export default createReactClass({
return joinRules.getContent().join_rule;
}
}
},
}
_communityProfile: function() {
_communityProfile() {
if (this.props.room) return CommunityPrototypeStore.instance.getInviteProfile(this.props.room.roomId);
return {displayName: null, avatarMxc: null};
},
}
_roomName: function(atStart = false) {
_roomName(atStart = false) {
let name = this.props.room ? this.props.room.name : this.props.roomAlias;
const profile = this._communityProfile();
if (profile.displayName) name = profile.displayName;
@ -249,16 +242,16 @@ export default createReactClass({
} else {
return _t("this room");
}
},
}
_getMyMember() {
return (
this.props.room &&
this.props.room.getMember(MatrixClientPeg.get().getUserId())
);
},
}
_getInviteMember: function() {
_getInviteMember() {
const {room} = this.props;
if (!room) {
return;
@ -270,7 +263,7 @@ export default createReactClass({
}
const inviterUserId = inviteEvent.events.member.getSender();
return room.currentState.getMember(inviterUserId);
},
}
_isDMInvite() {
const myMember = this._getMyMember();
@ -280,7 +273,7 @@ export default createReactClass({
const memberEvent = myMember.events.member;
const memberContent = memberEvent.getContent();
return memberContent.membership === "invite" && memberContent.is_direct;
},
}
_makeScreenAfterLogin() {
return {
@ -293,17 +286,17 @@ export default createReactClass({
inviter_name: this.props.oobData ? this.props.oobData.inviterName : null,
}
};
},
}
onLoginClick: function() {
onLoginClick = () => {
dis.dispatch({ action: 'start_login', screenAfterLogin: this._makeScreenAfterLogin() });
},
};
onRegisterClick: function() {
onRegisterClick = () => {
dis.dispatch({ action: 'start_registration', screenAfterLogin: this._makeScreenAfterLogin() });
},
};
render: function() {
render() {
const brand = SdkConfig.get().brand;
const Spinner = sdk.getComponent('elements.Spinner');
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
@ -597,5 +590,5 @@ export default createReactClass({
</div>
</div>
);
},
});
}
}

View file

@ -16,29 +16,26 @@ limitations under the License.
import React from 'react';
import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import * as sdk from '../../../index';
import Modal from '../../../Modal';
import { _t } from '../../../languageHandler';
import {MatrixClientPeg} from "../../../MatrixClientPeg";
export default createReactClass({
displayName: 'RoomUpgradeWarningBar',
propTypes: {
export default class RoomUpgradeWarningBar extends React.Component {
static propTypes = {
room: PropTypes.object.isRequired,
recommendation: PropTypes.object.isRequired,
},
};
componentDidMount: function() {
componentDidMount() {
const tombstone = this.props.room.currentState.getStateEvents("m.room.tombstone", "");
this.setState({upgraded: tombstone && tombstone.getContent().replacement_room});
MatrixClientPeg.get().on("RoomState.events", this._onStateEvents);
},
}
_onStateEvents: function(event, state) {
_onStateEvents = (event, state) => {
if (!this.props.room || event.getRoomId() !== this.props.room.roomId) {
return;
}
@ -47,14 +44,14 @@ export default createReactClass({
const tombstone = this.props.room.currentState.getStateEvents("m.room.tombstone", "");
this.setState({upgraded: tombstone && tombstone.getContent().replacement_room});
},
};
onUpgradeClick: function() {
onUpgradeClick = () => {
const RoomUpgradeDialog = sdk.getComponent('dialogs.RoomUpgradeDialog');
Modal.createTrackedDialog('Upgrade Room Version', '', RoomUpgradeDialog, {room: this.props.room});
},
};
render: function() {
render() {
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
let doUpgradeWarnings = (
@ -117,5 +114,5 @@ export default createReactClass({
</div>
</div>
);
},
});
}
}

View file

@ -15,35 +15,31 @@ limitations under the License.
*/
import React, {createRef} from 'react';
import createReactClass from 'create-react-class';
import AccessibleButton from "../elements/AccessibleButton";
import classNames from "classnames";
import { _t } from '../../../languageHandler';
import {Key} from "../../../Keyboard";
export default createReactClass({
displayName: 'SearchBar',
export default class SearchBar extends React.Component {
constructor(props) {
super(props);
getInitialState: function() {
return ({
scope: 'Room',
});
},
// TODO: [REACT-WARNING] Replace component with real class, use constructor for refs
UNSAFE_componentWillMount: function() {
this._search_term = createRef();
},
onThisRoomClick: function() {
this.state = {
scope: 'Room',
};
}
onThisRoomClick = () => {
this.setState({ scope: 'Room' }, () => this._searchIfQuery());
},
};
onAllRoomsClick: function() {
onAllRoomsClick = () => {
this.setState({ scope: 'All' }, () => this._searchIfQuery());
},
};
onSearchChange: function(e) {
onSearchChange = (e) => {
switch (e.key) {
case Key.ENTER:
this.onSearch();
@ -52,19 +48,19 @@ export default createReactClass({
this.props.onCancelClick();
break;
}
},
};
_searchIfQuery: function() {
_searchIfQuery() {
if (this._search_term.current.value) {
this.onSearch();
}
},
}
onSearch: function() {
onSearch = () => {
this.props.onSearch(this._search_term.current.value, this.state.scope);
},
};
render: function() {
render() {
const searchButtonClasses = classNames("mx_SearchBar_searchButton", {
mx_SearchBar_searching: this.props.searchInProgress,
});
@ -92,5 +88,5 @@ export default createReactClass({
<AccessibleButton className="mx_SearchBar_cancel" onClick={this.props.onCancelClick} />
</div>
);
},
});
}
}

View file

@ -17,14 +17,11 @@ limitations under the License.
import React from 'react';
import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import * as sdk from '../../../index';
import {haveTileForEvent} from "./EventTile";
export default createReactClass({
displayName: 'SearchResult',
propTypes: {
export default class SearchResultTile extends React.Component {
static propTypes = {
// a matrix-js-sdk SearchResult containing the details of this result
searchResult: PropTypes.object.isRequired,
@ -35,9 +32,9 @@ export default createReactClass({
resultLink: PropTypes.string,
onHeightChanged: PropTypes.func,
},
};
render: function() {
render() {
const DateSeparator = sdk.getComponent('messages.DateSeparator');
const EventTile = sdk.getComponent('rooms.EventTile');
const result = this.props.searchResult;
@ -66,5 +63,5 @@ export default createReactClass({
<li data-scroll-tokens={eventId+"+"+j}>
{ ret }
</li>);
},
});
}
}

View file

@ -99,8 +99,8 @@ export default class SendMessageComposer extends React.Component {
static contextType = MatrixClientContext;
constructor(props) {
super(props);
constructor(props, context) {
super(props, context);
this.model = null;
this._editorRef = null;
this.currentlyComposedEditorState = null;

View file

@ -16,7 +16,6 @@ limitations under the License.
import React from 'react';
import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import AccessibleButton from '../elements/AccessibleButton';
import * as sdk from '../../../index';
import { _t } from '../../../languageHandler';
@ -37,18 +36,16 @@ export function CancelButton(props) {
* A stripped-down room header used for things like the user settings
* and room directory.
*/
export default createReactClass({
displayName: 'SimpleRoomHeader',
propTypes: {
export default class SimpleRoomHeader extends React.Component {
static propTypes = {
title: PropTypes.string,
onCancelClick: PropTypes.func,
// `src` to a TintableSvg. Optional.
icon: PropTypes.string,
},
};
render: function() {
render() {
let cancelButton;
let icon;
if (this.props.onCancelClick) {
@ -73,5 +70,5 @@ export default createReactClass({
</div>
</div>
);
},
});
}
}

View file

@ -18,19 +18,16 @@ limitations under the License.
import React from 'react';
import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import { _t } from '../../../languageHandler';
import AccessibleButton from '../elements/AccessibleButton';
export default createReactClass({
displayName: 'TopUnreadMessagesBar',
propTypes: {
export default class TopUnreadMessagesBar extends React.Component {
static propTypes = {
onScrollUpClick: PropTypes.func,
onCloseClick: PropTypes.func,
},
};
render: function() {
render() {
return (
<div className="mx_TopUnreadMessagesBar">
<AccessibleButton className="mx_TopUnreadMessagesBar_scrollUp"
@ -43,5 +40,5 @@ export default createReactClass({
</AccessibleButton>
</div>
);
},
});
}
}

View file

@ -17,16 +17,13 @@ limitations under the License.
import React from 'react';
import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import * as WhoIsTyping from '../../../WhoIsTyping';
import Timer from '../../../utils/Timer';
import {MatrixClientPeg} from '../../../MatrixClientPeg';
import MemberAvatar from '../avatars/MemberAvatar';
export default createReactClass({
displayName: 'WhoIsTypingTile',
propTypes: {
export default class WhoIsTypingTile extends React.Component {
static propTypes = {
// the room this statusbar is representing.
room: PropTypes.object.isRequired,
onShown: PropTypes.func,
@ -34,32 +31,28 @@ export default createReactClass({
// Number of names to display in typing indication. E.g. set to 3, will
// result in "X, Y, Z and 100 others are typing."
whoIsTypingLimit: PropTypes.number,
},
};
getDefaultProps: function() {
return {
whoIsTypingLimit: 3,
};
},
static defaultProps = {
whoIsTypingLimit: 3,
};
getInitialState: function() {
return {
usersTyping: WhoIsTyping.usersTypingApartFromMe(this.props.room),
// a map with userid => Timer to delay
// hiding the "x is typing" message for a
// user so hiding it can coincide
// with the sent message by the other side
// resulting in less timeline jumpiness
delayedStopTypingTimers: {},
};
},
state = {
usersTyping: WhoIsTyping.usersTypingApartFromMe(this.props.room),
// a map with userid => Timer to delay
// hiding the "x is typing" message for a
// user so hiding it can coincide
// with the sent message by the other side
// resulting in less timeline jumpiness
delayedStopTypingTimers: {},
};
componentDidMount: function() {
componentDidMount() {
MatrixClientPeg.get().on("RoomMember.typing", this.onRoomMemberTyping);
MatrixClientPeg.get().on("Room.timeline", this.onRoomTimeline);
},
}
componentDidUpdate: function(_, prevState) {
componentDidUpdate(_, prevState) {
const wasVisible = this._isVisible(prevState);
const isVisible = this._isVisible(this.state);
if (this.props.onShown && !wasVisible && isVisible) {
@ -67,9 +60,9 @@ export default createReactClass({
} else if (this.props.onHidden && wasVisible && !isVisible) {
this.props.onHidden();
}
},
}
componentWillUnmount: function() {
componentWillUnmount() {
// we may have entirely lost our client as we're logging out before clicking login on the guest bar...
const client = MatrixClientPeg.get();
if (client) {
@ -77,17 +70,17 @@ export default createReactClass({
client.removeListener("Room.timeline", this.onRoomTimeline);
}
Object.values(this.state.delayedStopTypingTimers).forEach((t) => t.abort());
},
}
_isVisible: function(state) {
_isVisible(state) {
return state.usersTyping.length !== 0 || Object.keys(state.delayedStopTypingTimers).length !== 0;
},
}
isVisible: function() {
isVisible = () => {
return this._isVisible(this.state);
},
};
onRoomTimeline: function(event, room) {
onRoomTimeline = (event, room) => {
if (room && room.roomId === this.props.room.roomId) {
const userId = event.getSender();
// remove user from usersTyping
@ -96,15 +89,15 @@ export default createReactClass({
// abort timer if any
this._abortUserTimer(userId);
}
},
};
onRoomMemberTyping: function(ev, member) {
onRoomMemberTyping = (ev, member) => {
const usersTyping = WhoIsTyping.usersTypingApartFromMeAndIgnored(this.props.room);
this.setState({
delayedStopTypingTimers: this._updateDelayedStopTypingTimers(usersTyping),
usersTyping,
});
},
};
_updateDelayedStopTypingTimers(usersTyping) {
const usersThatStoppedTyping = this.state.usersTyping.filter((a) => {
@ -142,26 +135,26 @@ export default createReactClass({
}, delayedStopTypingTimers);
return delayedStopTypingTimers;
},
}
_abortUserTimer: function(userId) {
_abortUserTimer(userId) {
const timer = this.state.delayedStopTypingTimers[userId];
if (timer) {
timer.abort();
this._removeUserTimer(userId);
}
},
}
_removeUserTimer: function(userId) {
_removeUserTimer(userId) {
const timer = this.state.delayedStopTypingTimers[userId];
if (timer) {
const delayedStopTypingTimers = Object.assign({}, this.state.delayedStopTypingTimers);
delete delayedStopTypingTimers[userId];
this.setState({delayedStopTypingTimers});
}
},
}
_renderTypingIndicatorAvatars: function(users, limit) {
_renderTypingIndicatorAvatars(users, limit) {
let othersCount = 0;
if (users.length > limit) {
othersCount = users.length - limit + 1;
@ -190,9 +183,9 @@ export default createReactClass({
}
return avatars;
},
}
render: function() {
render() {
let usersTyping = this.state.usersTyping;
const stoppedUsersOnTimer = Object.keys(this.state.delayedStopTypingTimers)
.map((userId) => this.props.room.getMember(userId));
@ -222,5 +215,5 @@ export default createReactClass({
</div>
</li>
);
},
});
}
}

View file

@ -24,6 +24,7 @@ import {makeUserPermalink} from "../../../utils/permalinks/Permalinks";
import BaseAvatar from "../avatars/BaseAvatar";
import AccessibleButton from "../elements/AccessibleButton";
import {replaceableComponent} from "../../../utils/replaceableComponent";
import SettingsStore from "../../../settings/SettingsStore";
@replaceableComponent("views.settings.BridgeTile")
export default class BridgeTile extends React.PureComponent {
@ -56,7 +57,7 @@ export default class BridgeTile extends React.PureComponent {
type={Pill.TYPE_USER_MENTION}
room={this.props.room}
url={makeUserPermalink(content.creator)}
shouldShowPillAvatar={true}
shouldShowPillAvatar={SettingsStore.getValue("Pill.shouldShowPillAvatar")}
/>,
});
}
@ -66,7 +67,7 @@ export default class BridgeTile extends React.PureComponent {
type={Pill.TYPE_USER_MENTION}
room={this.props.room}
url={makeUserPermalink(this.props.ev.getSender())}
shouldShowPillAvatar={true}
shouldShowPillAvatar={SettingsStore.getValue("Pill.shouldShowPillAvatar")}
/>,
});

View file

@ -16,14 +16,12 @@ limitations under the License.
import React from 'react';
import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import {MatrixClientPeg} from "../../../MatrixClientPeg";
import * as sdk from '../../../index';
import { _t } from '../../../languageHandler';
export default createReactClass({
displayName: 'ChangeAvatar',
propTypes: {
export default class ChangeAvatar extends React.Component {
static propTypes = {
initialAvatarUrl: PropTypes.string,
room: PropTypes.object,
// if false, you need to call changeAvatar.onFileSelected yourself.
@ -31,36 +29,36 @@ export default createReactClass({
width: PropTypes.number,
height: PropTypes.number,
className: PropTypes.string,
},
};
Phases: {
static Phases = {
Display: "display",
Uploading: "uploading",
Error: "error",
},
};
getDefaultProps: function() {
return {
showUploadSection: true,
className: "",
width: 80,
height: 80,
};
},
static defaultProps = {
showUploadSection: true,
className: "",
width: 80,
height: 80,
};
getInitialState: function() {
return {
constructor(props) {
super(props);
this.state = {
avatarUrl: this.props.initialAvatarUrl,
phase: this.Phases.Display,
phase: ChangeAvatar.Phases.Display,
};
},
}
componentDidMount: function() {
componentDidMount() {
MatrixClientPeg.get().on("RoomState.events", this.onRoomStateEvents);
},
}
// TODO: [REACT-WARNING] Replace with appropriate lifecycle event
UNSAFE_componentWillReceiveProps: function(newProps) {
UNSAFE_componentWillReceiveProps(newProps) {
if (this.avatarSet) {
// don't clobber what the user has just set
return;
@ -68,15 +66,15 @@ export default createReactClass({
this.setState({
avatarUrl: newProps.initialAvatarUrl,
});
},
}
componentWillUnmount: function() {
componentWillUnmount() {
if (MatrixClientPeg.get()) {
MatrixClientPeg.get().removeListener("RoomState.events", this.onRoomStateEvents);
}
},
}
onRoomStateEvents: function(ev) {
onRoomStateEvents = (ev) => {
if (!this.props.room) {
return;
}
@ -90,13 +88,13 @@ export default createReactClass({
this.avatarSet = false;
this.setState({}); // force update
}
},
};
setAvatarFromFile: function(file) {
setAvatarFromFile(file) {
let newUrl = null;
this.setState({
phase: this.Phases.Uploading,
phase: ChangeAvatar.Phases.Uploading,
});
const self = this;
const httpPromise = MatrixClientPeg.get().uploadContent(file).then(function(url) {
@ -115,31 +113,31 @@ export default createReactClass({
httpPromise.then(function() {
self.setState({
phase: self.Phases.Display,
phase: ChangeAvatar.Phases.Display,
avatarUrl: MatrixClientPeg.get().mxcUrlToHttp(newUrl),
});
}, function(error) {
self.setState({
phase: self.Phases.Error,
phase: ChangeAvatar.Phases.Error,
});
self.onError(error);
});
return httpPromise;
},
}
onFileSelected: function(ev) {
onFileSelected = (ev) => {
this.avatarSet = true;
return this.setAvatarFromFile(ev.target.files[0]);
},
};
onError: function(error) {
onError = (error) => {
this.setState({
errorText: _t("Failed to upload profile picture!"),
});
},
};
render: function() {
render() {
let avatarImg;
// Having just set an avatar we just display that since it will take a little
// time to propagate through to the RoomAvatar.
@ -165,8 +163,8 @@ export default createReactClass({
}
switch (this.state.phase) {
case this.Phases.Display:
case this.Phases.Error:
case ChangeAvatar.Phases.Display:
case ChangeAvatar.Phases.Error:
return (
<div>
<div className={this.props.className}>
@ -175,11 +173,11 @@ export default createReactClass({
{ uploadSection }
</div>
);
case this.Phases.Uploading:
case ChangeAvatar.Phases.Uploading:
var Loader = sdk.getComponent("elements.Spinner");
return (
<Loader />
);
}
},
});
}
}

View file

@ -17,15 +17,12 @@ limitations under the License.
*/
import React from 'react';
import createReactClass from 'create-react-class';
import * as sdk from '../../../index';
import {MatrixClientPeg} from '../../../MatrixClientPeg';
import { _t } from '../../../languageHandler';
export default createReactClass({
displayName: 'ChangeDisplayName',
_getDisplayName: async function() {
export default class ChangeDisplayName extends React.Component {
_getDisplayName = async () => {
const cli = MatrixClientPeg.get();
try {
const res = await cli.getProfileInfo(cli.getUserId());
@ -33,16 +30,16 @@ export default createReactClass({
} catch (e) {
throw new Error("Failed to fetch display name");
}
},
};
_changeDisplayName: function(newDisplayname) {
_changeDisplayName = (newDisplayname) => {
const cli = MatrixClientPeg.get();
return cli.setDisplayName(newDisplayname).catch(function(e) {
throw new Error("Failed to set display name", e);
});
},
};
render: function() {
render() {
const EditableTextContainer = sdk.getComponent('elements.EditableTextContainer');
return (
<EditableTextContainer
@ -51,5 +48,5 @@ export default createReactClass({
blurToSubmit={true}
onSubmit={this._changeDisplayName} />
);
},
});
}
}

View file

@ -18,7 +18,6 @@ limitations under the License.
import Field from "../elements/Field";
import React from 'react';
import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import {MatrixClientPeg} from "../../../MatrixClientPeg";
import dis from "../../../dispatcher/dispatcher";
import AccessibleButton from '../elements/AccessibleButton';
@ -28,10 +27,8 @@ import Modal from "../../../Modal";
import sessionStore from '../../../stores/SessionStore';
export default createReactClass({
displayName: 'ChangePassword',
propTypes: {
export default class ChangePassword extends React.Component {
static propTypes = {
onFinished: PropTypes.func,
onError: PropTypes.func,
onCheckPassword: PropTypes.func,
@ -41,65 +38,61 @@ export default createReactClass({
confirm: PropTypes.bool,
// Whether to autoFocus the new password input
autoFocusNewPasswordInput: PropTypes.bool,
},
};
Phases: {
static Phases = {
Edit: "edit",
Uploading: "uploading",
Error: "error",
},
};
getDefaultProps: function() {
return {
onFinished: function() {},
onError: function() {},
onCheckPassword: function(oldPass, newPass, confirmPass) {
if (newPass !== confirmPass) {
return {
error: _t("New passwords don't match"),
};
} else if (!newPass || newPass.length === 0) {
return {
error: _t("Passwords can't be empty"),
};
}
},
confirm: true,
};
},
static defaultProps = {
onFinished() {},
onError() {},
onCheckPassword(oldPass, newPass, confirmPass) {
if (newPass !== confirmPass) {
return {
error: _t("New passwords don't match"),
};
} else if (!newPass || newPass.length === 0) {
return {
error: _t("Passwords can't be empty"),
};
}
},
confirm: true,
}
getInitialState: function() {
return {
phase: this.Phases.Edit,
cachedPassword: null,
oldPassword: "",
newPassword: "",
newPasswordConfirm: "",
};
},
state = {
phase: ChangePassword.Phases.Edit,
cachedPassword: null,
oldPassword: "",
newPassword: "",
newPasswordConfirm: "",
};
componentDidMount: function() {
componentDidMount() {
this._sessionStore = sessionStore;
this._sessionStoreToken = this._sessionStore.addListener(
this._setStateFromSessionStore,
);
this._setStateFromSessionStore();
},
}
componentWillUnmount: function() {
componentWillUnmount() {
if (this._sessionStoreToken) {
this._sessionStoreToken.remove();
}
},
}
_setStateFromSessionStore: function() {
_setStateFromSessionStore = () => {
this.setState({
cachedPassword: this._sessionStore.getCachedPassword(),
});
},
};
changePassword: function(oldPassword, newPassword) {
changePassword(oldPassword, newPassword) {
const cli = MatrixClientPeg.get();
if (!this.props.confirm) {
@ -136,9 +129,9 @@ export default createReactClass({
}
},
});
},
}
_changePassword: function(cli, oldPassword, newPassword) {
_changePassword(cli, oldPassword, newPassword) {
const authDict = {
type: 'm.login.password',
identifier: {
@ -152,7 +145,7 @@ export default createReactClass({
};
this.setState({
phase: this.Phases.Uploading,
phase: ChangePassword.Phases.Uploading,
});
cli.setPassword(authDict, newPassword).then(() => {
@ -172,51 +165,51 @@ export default createReactClass({
this.props.onError(err);
}).finally(() => {
this.setState({
phase: this.Phases.Edit,
phase: ChangePassword.Phases.Edit,
oldPassword: "",
newPassword: "",
newPasswordConfirm: "",
});
});
},
}
_optionallySetEmail: function() {
_optionallySetEmail() {
// Ask for an email otherwise the user has no way to reset their password
const SetEmailDialog = sdk.getComponent("dialogs.SetEmailDialog");
const modal = Modal.createTrackedDialog('Do you want to set an email address?', '', SetEmailDialog, {
title: _t('Do you want to set an email address?'),
});
return modal.finished.then(([confirmed]) => confirmed);
},
}
_onExportE2eKeysClicked: function() {
_onExportE2eKeysClicked = () => {
Modal.createTrackedDialogAsync('Export E2E Keys', 'Change Password',
import('../../../async-components/views/dialogs/ExportE2eKeysDialog'),
{
matrixClient: MatrixClientPeg.get(),
},
);
},
};
onChangeOldPassword(ev) {
onChangeOldPassword = (ev) => {
this.setState({
oldPassword: ev.target.value,
});
},
};
onChangeNewPassword(ev) {
onChangeNewPassword = (ev) => {
this.setState({
newPassword: ev.target.value,
});
},
};
onChangeNewPasswordConfirm(ev) {
onChangeNewPasswordConfirm = (ev) => {
this.setState({
newPasswordConfirm: ev.target.value,
});
},
};
onClickChange: function(ev) {
onClickChange = (ev) => {
ev.preventDefault();
const oldPassword = this.state.cachedPassword || this.state.oldPassword;
const newPassword = this.state.newPassword;
@ -229,9 +222,9 @@ export default createReactClass({
} else {
this.changePassword(oldPassword, newPassword);
}
},
};
render: function() {
render() {
// TODO: Live validation on `new pw == confirm pw`
const rowClassName = this.props.rowClassName;
@ -252,7 +245,7 @@ export default createReactClass({
}
switch (this.state.phase) {
case this.Phases.Edit:
case ChangePassword.Phases.Edit:
const passwordLabel = this.state.cachedPassword ?
_t('Password') : _t('New Password');
return (
@ -282,7 +275,7 @@ export default createReactClass({
</AccessibleButton>
</form>
);
case this.Phases.Uploading:
case ChangePassword.Phases.Uploading:
var Loader = sdk.getComponent("elements.Spinner");
return (
<div className="mx_Dialog_content">
@ -290,5 +283,5 @@ export default createReactClass({
</div>
);
}
},
});
}
}

View file

@ -19,8 +19,9 @@ import React from 'react';
import {MatrixClientPeg} from '../../../MatrixClientPeg';
import { _t } from '../../../languageHandler';
import * as sdk from '../../../index';
import { accessSecretStorage } from '../../../CrossSigningManager';
import { accessSecretStorage } from '../../../SecurityManager';
import Modal from '../../../Modal';
import Spinner from '../elements/Spinner';
export default class CrossSigningPanel extends React.PureComponent {
constructor(props) {
@ -163,8 +164,7 @@ export default class CrossSigningPanel extends React.PureComponent {
let summarisedStatus;
if (homeserverSupportsCrossSigning === undefined) {
const InlineSpinner = sdk.getComponent('views.elements.InlineSpinner');
summarisedStatus = <p><InlineSpinner /></p>;
summarisedStatus = <Spinner />;
} else if (!homeserverSupportsCrossSigning) {
summarisedStatus = <p>{_t(
"Your homeserver does not support cross-signing.",

View file

@ -25,7 +25,7 @@ const SETTING_MANUALLY_VERIFY_ALL_SESSIONS = "e2ee.manuallyVerifyAllSessions";
const E2eAdvancedPanel = props => {
const SettingsFlag = sdk.getComponent('views.elements.SettingsFlag');
return <div className="mx_SettingsTab_section">
<span className="mx_SettingsTab_subheading">{_t("Advanced")}</span>
<span className="mx_SettingsTab_subheading">{_t("Encryption")}</span>
<SettingsFlag name={SETTING_MANUALLY_VERIFY_ALL_SESSIONS}
level={SettingLevel.DEVICE}

View file

@ -16,7 +16,6 @@ limitations under the License.
*/
import React from 'react';
import createReactClass from 'create-react-class';
import * as sdk from '../../../index';
import { _t } from '../../../languageHandler';
import {MatrixClientPeg} from '../../../MatrixClientPeg';
@ -65,46 +64,42 @@ function portLegacyActions(actions) {
}
}
export default createReactClass({
displayName: 'Notifications',
phases: {
export default class Notifications extends React.Component {
static phases = {
LOADING: "LOADING", // The component is loading or sending data to the hs
DISPLAY: "DISPLAY", // The component is ready and display data
ERROR: "ERROR", // There was an error
},
};
getInitialState: function() {
return {
phase: this.phases.LOADING,
masterPushRule: undefined, // The master rule ('.m.rule.master')
vectorPushRules: [], // HS default push rules displayed in Vector UI
vectorContentRules: { // Keyword push rules displayed in Vector UI
vectorState: PushRuleVectorState.ON,
rules: [],
},
externalPushRules: [], // Push rules (except content rule) that have been defined outside Vector UI
externalContentRules: [], // Keyword push rules that have been defined outside Vector UI
threepids: [], // used for email notifications
};
},
state = {
phase: Notifications.phases.LOADING,
masterPushRule: undefined, // The master rule ('.m.rule.master')
vectorPushRules: [], // HS default push rules displayed in Vector UI
vectorContentRules: { // Keyword push rules displayed in Vector UI
vectorState: PushRuleVectorState.ON,
rules: [],
},
externalPushRules: [], // Push rules (except content rule) that have been defined outside Vector UI
externalContentRules: [], // Keyword push rules that have been defined outside Vector UI
threepids: [], // used for email notifications
};
componentDidMount: function() {
componentDidMount() {
this._refreshFromServer();
},
}
onEnableNotificationsChange: function(checked) {
onEnableNotificationsChange = (checked) => {
const self = this;
this.setState({
phase: this.phases.LOADING,
phase: Notifications.phases.LOADING,
});
MatrixClientPeg.get().setPushRuleEnabled('global', self.state.masterPushRule.kind, self.state.masterPushRule.rule_id, !checked).then(function() {
self._refreshFromServer();
});
},
};
onEnableDesktopNotificationsChange: function(checked) {
onEnableDesktopNotificationsChange = (checked) => {
SettingsStore.setValue(
"notificationsEnabled", null,
SettingLevel.DEVICE,
@ -112,9 +107,9 @@ export default createReactClass({
).finally(() => {
this.forceUpdate();
});
},
};
onEnableDesktopNotificationBodyChange: function(checked) {
onEnableDesktopNotificationBodyChange = (checked) => {
SettingsStore.setValue(
"notificationBodyEnabled", null,
SettingLevel.DEVICE,
@ -122,9 +117,9 @@ export default createReactClass({
).finally(() => {
this.forceUpdate();
});
},
};
onEnableAudioNotificationsChange: function(checked) {
onEnableAudioNotificationsChange = (checked) => {
SettingsStore.setValue(
"audioNotificationsEnabled", null,
SettingLevel.DEVICE,
@ -132,7 +127,7 @@ export default createReactClass({
).finally(() => {
this.forceUpdate();
});
},
};
/*
* Returns the email pusher (pusher of type 'email') for a given
@ -140,7 +135,7 @@ export default createReactClass({
* pushers are unique over (app ID, pushkey), there will be at most
* one such pusher.
*/
getEmailPusher: function(pushers, address) {
getEmailPusher(pushers, address) {
if (pushers === undefined) {
return undefined;
}
@ -150,9 +145,9 @@ export default createReactClass({
}
}
return undefined;
},
}
onEnableEmailNotificationsChange: function(address, checked) {
onEnableEmailNotificationsChange = (address, checked) => {
let emailPusherPromise;
if (checked) {
const data = {};
@ -181,9 +176,9 @@ export default createReactClass({
description: _t('An error occurred whilst saving your email notification preferences.'),
});
});
},
};
onNotifStateButtonClicked: function(event) {
onNotifStateButtonClicked = (event) => {
// FIXME: use .bind() rather than className metadata here surely
const vectorRuleId = event.target.className.split("-")[0];
const newPushRuleVectorState = event.target.className.split("-")[1];
@ -196,11 +191,9 @@ export default createReactClass({
this._setPushRuleVectorState(rule, newPushRuleVectorState);
}
}
},
onKeywordsClicked: function(event) {
const self = this;
};
onKeywordsClicked = (event) => {
// Compute the keywords list to display
let keywords = [];
for (const i in this.state.vectorContentRules.rules) {
@ -223,7 +216,7 @@ export default createReactClass({
description: _t('Enter keywords separated by a comma:'),
button: _t('OK'),
value: keywords,
onFinished: function onFinished(should_leave, newValue) {
onFinished: (should_leave, newValue) => {
if (should_leave && newValue !== keywords) {
let newKeywords = newValue.split(',');
for (const i in newKeywords) {
@ -238,25 +231,25 @@ export default createReactClass({
return array;
}, []);
self._setKeywords(newKeywords);
this._setKeywords(newKeywords);
}
},
});
},
};
getRule: function(vectorRuleId) {
getRule(vectorRuleId) {
for (const i in this.state.vectorPushRules) {
const rule = this.state.vectorPushRules[i];
if (rule.vectorRuleId === vectorRuleId) {
return rule;
}
}
},
}
_setPushRuleVectorState: function(rule, newPushRuleVectorState) {
_setPushRuleVectorState(rule, newPushRuleVectorState) {
if (rule && rule.vectorState !== newPushRuleVectorState) {
this.setState({
phase: this.phases.LOADING,
phase: Notifications.phases.LOADING,
});
const self = this;
@ -288,9 +281,9 @@ export default createReactClass({
});
});
}
},
}
_setKeywordsPushRuleVectorState: function(newPushRuleVectorState) {
_setKeywordsPushRuleVectorState(newPushRuleVectorState) {
// Is there really a change?
if (this.state.vectorContentRules.vectorState === newPushRuleVectorState
|| this.state.vectorContentRules.rules.length === 0) {
@ -301,7 +294,7 @@ export default createReactClass({
const cli = MatrixClientPeg.get();
this.setState({
phase: this.phases.LOADING,
phase: Notifications.phases.LOADING,
});
// Update all rules in self.state.vectorContentRules
@ -356,11 +349,11 @@ export default createReactClass({
onFinished: self._refreshFromServer,
});
});
},
}
_setKeywords: function(newKeywords) {
_setKeywords(newKeywords) {
this.setState({
phase: this.phases.LOADING,
phase: Notifications.phases.LOADING,
});
const self = this;
@ -440,19 +433,19 @@ export default createReactClass({
self._refreshFromServer();
}, onError);
}, onError);
},
}
// Create a push rule but disabled
_addDisabledPushRule: function(scope, kind, ruleId, body) {
_addDisabledPushRule(scope, kind, ruleId, body) {
const cli = MatrixClientPeg.get();
return cli.addPushRule(scope, kind, ruleId, body).then(() =>
cli.setPushRuleEnabled(scope, kind, ruleId, false),
);
},
}
// Check if any legacy im.vector rules need to be ported to the new API
// for overriding the actions of default rules.
_portRulesToNewAPI: function(rulesets) {
_portRulesToNewAPI(rulesets) {
const needsUpdate = [];
const cli = MatrixClientPeg.get();
@ -485,9 +478,9 @@ export default createReactClass({
// Otherwise return the rules that we already have.
return rulesets;
}
},
}
_refreshFromServer: function() {
_refreshFromServer = () => {
const self = this;
const pushRulesPromise = MatrixClientPeg.get().getPushRules().then(self._portRulesToNewAPI).then(function(rulesets) {
/// XXX seriously? wtf is this?
@ -636,12 +629,12 @@ export default createReactClass({
Promise.all([pushRulesPromise, pushersPromise]).then(function() {
self.setState({
phase: self.phases.DISPLAY,
phase: Notifications.phases.DISPLAY,
});
}, function(error) {
console.error(error);
self.setState({
phase: self.phases.ERROR,
phase: Notifications.phases.ERROR,
});
}).finally(() => {
// actually explicitly update our state having been deep-manipulating it
@ -655,9 +648,9 @@ export default createReactClass({
});
MatrixClientPeg.get().getThreePids().then((r) => this.setState({threepids: r.threepids}));
},
};
_onClearNotifications: function() {
_onClearNotifications = () => {
const cli = MatrixClientPeg.get();
cli.getRooms().forEach(r => {
@ -666,9 +659,9 @@ export default createReactClass({
if (events.length) cli.sendReadReceipt(events.pop());
}
});
},
};
_updatePushRuleActions: function(rule, actions, enabled) {
_updatePushRuleActions(rule, actions, enabled) {
const cli = MatrixClientPeg.get();
return cli.setPushRuleActions(
@ -681,9 +674,9 @@ export default createReactClass({
);
}
});
},
}
renderNotifRulesTableRow: function(title, className, pushRuleVectorState) {
renderNotifRulesTableRow(title, className, pushRuleVectorState) {
return (
<tr key={ className }>
<th>
@ -712,9 +705,9 @@ export default createReactClass({
</th>
</tr>
);
},
}
renderNotifRulesTableRows: function() {
renderNotifRulesTableRows() {
const rows = [];
for (const i in this.state.vectorPushRules) {
const rule = this.state.vectorPushRules[i];
@ -726,9 +719,9 @@ export default createReactClass({
rows.push(this.renderNotifRulesTableRow(rule.description, rule.vectorRuleId, rule.vectorState));
}
return rows;
},
}
hasEmailPusher: function(pushers, address) {
hasEmailPusher(pushers, address) {
if (pushers === undefined) {
return false;
}
@ -738,17 +731,17 @@ export default createReactClass({
}
}
return false;
},
}
emailNotificationsRow: function(address, label) {
emailNotificationsRow(address, label) {
return <LabelledToggleSwitch value={this.hasEmailPusher(this.state.pushers, address)}
onChange={this.onEnableEmailNotificationsChange.bind(this, address)}
label={label} key={`emailNotif_${label}`} />;
},
}
render: function() {
render() {
let spinner;
if (this.state.phase === this.phases.LOADING) {
if (this.state.phase === Notifications.phases.LOADING) {
const Loader = sdk.getComponent("elements.Spinner");
spinner = <Loader />;
}
@ -910,5 +903,5 @@ export default createReactClass({
</div>
);
},
});
}
}

View file

@ -332,9 +332,8 @@ export default class SecurityUserSettingsTab extends React.Component {
return (
<div className="mx_SettingsTab mx_SecurityUserSettingsTab">
{warning}
<div className="mx_SettingsTab_heading">{_t("Security & Privacy")}</div>
<div className="mx_SettingsTab_heading">{_t("Where youre logged in")}</div>
<div className="mx_SettingsTab_section">
<span className="mx_SettingsTab_subheading">{_t("Where youre logged in")}</span>
<span>
{_t(
"Manage the names of and sign out of your sessions below or " +
@ -351,11 +350,15 @@ export default class SecurityUserSettingsTab extends React.Component {
<DevicesPanel />
</div>
</div>
{keyBackup}
{eventIndex}
{crossSigning}
{this._renderCurrentDeviceInfo()}
<div className='mx_SettingsTab_section'>
<div className="mx_SettingsTab_heading">{_t("Encryption")}</div>
<div className="mx_SettingsTab_section">
{keyBackup}
{eventIndex}
{crossSigning}
{this._renderCurrentDeviceInfo()}
</div>
<div className="mx_SettingsTab_heading">{_t("Privacy")}</div>
<div className="mx_SettingsTab_section">
<span className="mx_SettingsTab_subheading">{_t("Analytics")}</span>
<div className='mx_SettingsTab_subsectionText'>
{_t(
@ -372,9 +375,12 @@ export default class SecurityUserSettingsTab extends React.Component {
<SettingsFlag name='analyticsOptIn' level={SettingLevel.DEVICE}
onChange={this._updateAnalytics} />
</div>
{this._renderIgnoredUsers()}
{this._renderManageInvites()}
<E2eAdvancedPanel />
<div className="mx_SettingsTab_heading">{_t("Advanced")}</div>
<div className="mx_SettingsTab_section">
{this._renderIgnoredUsers()}
{this._renderManageInvites()}
<E2eAdvancedPanel />
</div>
</div>
);
}

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