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:
commit
667c129ebc
162 changed files with 4010 additions and 3800 deletions
|
@ -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>
|
||||
);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
167
src/components/views/dialogs/EditCommunityPrototypeDialog.tsx
Normal file
167
src/components/views/dialogs/EditCommunityPrototypeDialog.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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>
|
||||
);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)} />;
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>;
|
||||
|
|
|
@ -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>;
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>;
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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}
|
||||
/>
|
||||
);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>;
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>;
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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}
|
||||
/>;
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>;
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 [
|
||||
|
|
|
@ -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>({
|
||||
|
|
|
@ -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 [
|
||||
|
|
|
@ -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]);
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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';
|
||||
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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'];
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
});
|
||||
},
|
||||
});
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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}
|
||||
/>
|
||||
);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>;
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>;
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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")}
|
||||
/>,
|
||||
});
|
||||
|
||||
|
|
|
@ -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 />
|
||||
);
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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} />
|
||||
);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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.",
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 you’re logged in")}</div>
|
||||
<div className="mx_SettingsTab_section">
|
||||
<span className="mx_SettingsTab_subheading">{_t("Where you’re 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
Loading…
Add table
Add a link
Reference in a new issue