mirror of
https://github.com/element-hq/element-web.git
synced 2024-12-17 01:21:47 +03:00
Merge branches 'develop' and 't3chguy/m.relates_to' of github.com:matrix-org/matrix-react-sdk into t3chguy/m.relates_to
This commit is contained in:
commit
a15d024ad3
10 changed files with 110 additions and 23 deletions
|
@ -1,3 +1,9 @@
|
||||||
|
Changes in [0.11.4](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v0.11.4) (2018-02-09)
|
||||||
|
=====================================================================================================
|
||||||
|
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v0.11.3...v0.11.4)
|
||||||
|
|
||||||
|
* Add isUrlPermitted function to sanity check URLs
|
||||||
|
|
||||||
Changes in [0.11.3](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v0.11.3) (2017-12-04)
|
Changes in [0.11.3](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v0.11.3) (2017-12-04)
|
||||||
=====================================================================================================
|
=====================================================================================================
|
||||||
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v0.11.2...v0.11.3)
|
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v0.11.2...v0.11.3)
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "matrix-react-sdk",
|
"name": "matrix-react-sdk",
|
||||||
"version": "0.11.3",
|
"version": "0.11.4",
|
||||||
"description": "SDK for matrix.org using React",
|
"description": "SDK for matrix.org using React",
|
||||||
"author": "matrix.org",
|
"author": "matrix.org",
|
||||||
"repository": {
|
"repository": {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2015, 2016 OpenMarket Ltd
|
Copyright 2015, 2016 OpenMarket Ltd
|
||||||
Copyright 2017 New Vector Ltd
|
Copyright 2017, 2018 New Vector Ltd
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
@ -25,6 +25,7 @@ import escape from 'lodash/escape';
|
||||||
import emojione from 'emojione';
|
import emojione from 'emojione';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import MatrixClientPeg from './MatrixClientPeg';
|
import MatrixClientPeg from './MatrixClientPeg';
|
||||||
|
import url from 'url';
|
||||||
|
|
||||||
emojione.imagePathSVG = 'emojione/svg/';
|
emojione.imagePathSVG = 'emojione/svg/';
|
||||||
// Store PNG path for displaying many flags at once (for increased performance over SVG)
|
// Store PNG path for displaying many flags at once (for increased performance over SVG)
|
||||||
|
@ -44,6 +45,8 @@ const SYMBOL_PATTERN = /([\u2100-\u2bff])/;
|
||||||
const EMOJI_REGEX = new RegExp(emojione.unicodeRegexp+"+", "gi");
|
const EMOJI_REGEX = new RegExp(emojione.unicodeRegexp+"+", "gi");
|
||||||
const COLOR_REGEX = /^#[0-9a-fA-F]{6}$/;
|
const COLOR_REGEX = /^#[0-9a-fA-F]{6}$/;
|
||||||
|
|
||||||
|
const PERMITTED_URL_SCHEMES = ['http', 'https', 'ftp', 'mailto', 'magnet'];
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Return true if the given string contains emoji
|
* Return true if the given string contains emoji
|
||||||
* Uses a much, much simpler regex than emojione's so will give false
|
* Uses a much, much simpler regex than emojione's so will give false
|
||||||
|
@ -152,6 +155,25 @@ export function sanitizedHtmlNode(insaneHtml) {
|
||||||
return <div dangerouslySetInnerHTML={{ __html: saneHtml }} dir="auto" />;
|
return <div dangerouslySetInnerHTML={{ __html: saneHtml }} dir="auto" />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests if a URL from an untrusted source may be safely put into the DOM
|
||||||
|
* The biggest threat here is javascript: URIs.
|
||||||
|
* Note that the HTML sanitiser library has its own internal logic for
|
||||||
|
* doing this, to which we pass the same list of schemes. This is used in
|
||||||
|
* other places we need to sanitise URLs.
|
||||||
|
* @return true if permitted, otherwise false
|
||||||
|
*/
|
||||||
|
export function isUrlPermitted(inputUrl) {
|
||||||
|
try {
|
||||||
|
const parsed = url.parse(inputUrl);
|
||||||
|
if (!parsed.protocol) return false;
|
||||||
|
// URL parser protocol includes the trailing colon
|
||||||
|
return PERMITTED_URL_SCHEMES.includes(parsed.protocol.slice(0, -1));
|
||||||
|
} catch (e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const sanitizeHtmlParams = {
|
const sanitizeHtmlParams = {
|
||||||
allowedTags: [
|
allowedTags: [
|
||||||
'font', // custom to matrix for IRC-style font coloring
|
'font', // custom to matrix for IRC-style font coloring
|
||||||
|
@ -172,7 +194,7 @@ const sanitizeHtmlParams = {
|
||||||
// Lots of these won't come up by default because we don't allow them
|
// Lots of these won't come up by default because we don't allow them
|
||||||
selfClosing: ['img', 'br', 'hr', 'area', 'base', 'basefont', 'input', 'link', 'meta'],
|
selfClosing: ['img', 'br', 'hr', 'area', 'base', 'basefont', 'input', 'link', 'meta'],
|
||||||
// URL schemes we permit
|
// URL schemes we permit
|
||||||
allowedSchemes: ['http', 'https', 'ftp', 'mailto', 'magnet'],
|
allowedSchemes: PERMITTED_URL_SCHEMES,
|
||||||
|
|
||||||
allowProtocolRelative: false,
|
allowProtocolRelative: false,
|
||||||
|
|
||||||
|
|
|
@ -618,18 +618,26 @@ export default React.createClass({
|
||||||
},
|
},
|
||||||
|
|
||||||
_startRegistration: function(params) {
|
_startRegistration: function(params) {
|
||||||
this.setStateForNewView({
|
const newState = {
|
||||||
view: VIEWS.REGISTER,
|
view: VIEWS.REGISTER,
|
||||||
// these params may be undefined, but if they are,
|
};
|
||||||
// unset them from our state: we don't want to
|
|
||||||
// resume a previous registration session if the
|
// Only honour params if they are all present, otherwise we reset
|
||||||
// user just clicked 'register'
|
// HS and IS URLs when switching to registration.
|
||||||
register_client_secret: params.client_secret,
|
if (params.client_secret &&
|
||||||
register_session_id: params.session_id,
|
params.session_id &&
|
||||||
register_hs_url: params.hs_url,
|
params.hs_url &&
|
||||||
register_is_url: params.is_url,
|
params.is_url &&
|
||||||
register_id_sid: params.sid,
|
params.sid
|
||||||
});
|
) {
|
||||||
|
newState.register_client_secret = params.client_secret;
|
||||||
|
newState.register_session_id = params.session_id;
|
||||||
|
newState.register_hs_url = params.hs_url;
|
||||||
|
newState.register_is_url = params.is_url;
|
||||||
|
newState.register_id_sid = params.sid;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setStateForNewView(newState);
|
||||||
this.notifyNewScreen('register');
|
this.notifyNewScreen('register');
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -1501,6 +1509,17 @@ export default React.createClass({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
onServerConfigChange(config) {
|
||||||
|
const newState = {};
|
||||||
|
if (config.hsUrl) {
|
||||||
|
newState.register_hs_url = config.hsUrl;
|
||||||
|
}
|
||||||
|
if (config.isUrl) {
|
||||||
|
newState.register_is_url = config.isUrl;
|
||||||
|
}
|
||||||
|
this.setState(newState);
|
||||||
|
},
|
||||||
|
|
||||||
_makeRegistrationUrl: function(params) {
|
_makeRegistrationUrl: function(params) {
|
||||||
if (this.props.startingFragmentQueryParams.referrer) {
|
if (this.props.startingFragmentQueryParams.referrer) {
|
||||||
params.referrer = this.props.startingFragmentQueryParams.referrer;
|
params.referrer = this.props.startingFragmentQueryParams.referrer;
|
||||||
|
@ -1589,6 +1608,7 @@ export default React.createClass({
|
||||||
onLoginClick={this.onLoginClick}
|
onLoginClick={this.onLoginClick}
|
||||||
onRegisterClick={this.onRegisterClick}
|
onRegisterClick={this.onRegisterClick}
|
||||||
onCancelClick={MatrixClientPeg.get() ? this.onReturnToAppClick : null}
|
onCancelClick={MatrixClientPeg.get() ? this.onReturnToAppClick : null}
|
||||||
|
onServerConfigChange={this.onServerConfigChange}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1623,6 +1643,7 @@ export default React.createClass({
|
||||||
onForgotPasswordClick={this.onForgotPasswordClick}
|
onForgotPasswordClick={this.onForgotPasswordClick}
|
||||||
enableGuest={this.props.enableGuest}
|
enableGuest={this.props.enableGuest}
|
||||||
onCancelClick={MatrixClientPeg.get() ? this.onReturnToAppClick : null}
|
onCancelClick={MatrixClientPeg.get() ? this.onReturnToAppClick : null}
|
||||||
|
onServerConfigChange={this.onServerConfigChange}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -264,12 +264,19 @@ module.exports = React.createClass({
|
||||||
isPeeking: true, // this will change to false if peeking fails
|
isPeeking: true, // this will change to false if peeking fails
|
||||||
});
|
});
|
||||||
MatrixClientPeg.get().peekInRoom(roomId).then((room) => {
|
MatrixClientPeg.get().peekInRoom(roomId).then((room) => {
|
||||||
|
if (this.unmounted) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.setState({
|
this.setState({
|
||||||
room: room,
|
room: room,
|
||||||
peekLoading: false,
|
peekLoading: false,
|
||||||
});
|
});
|
||||||
this._onRoomLoaded(room);
|
this._onRoomLoaded(room);
|
||||||
}, (err) => {
|
}, (err) => {
|
||||||
|
if (this.unmounted) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Stop peeking if anything went wrong
|
// Stop peeking if anything went wrong
|
||||||
this.setState({
|
this.setState({
|
||||||
isPeeking: false,
|
isPeeking: false,
|
||||||
|
@ -286,7 +293,7 @@ module.exports = React.createClass({
|
||||||
} else {
|
} else {
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
}).done();
|
});
|
||||||
}
|
}
|
||||||
} else if (room) {
|
} else if (room) {
|
||||||
// Stop peeking because we have joined this room previously
|
// Stop peeking because we have joined this room previously
|
||||||
|
|
|
@ -58,6 +58,7 @@ module.exports = React.createClass({
|
||||||
// login shouldn't care how password recovery is done.
|
// login shouldn't care how password recovery is done.
|
||||||
onForgotPasswordClick: PropTypes.func,
|
onForgotPasswordClick: PropTypes.func,
|
||||||
onCancelClick: PropTypes.func,
|
onCancelClick: PropTypes.func,
|
||||||
|
onServerConfigChange: PropTypes.func.isRequired,
|
||||||
},
|
},
|
||||||
|
|
||||||
getInitialState: function() {
|
getInitialState: function() {
|
||||||
|
@ -218,6 +219,8 @@ module.exports = React.createClass({
|
||||||
if (config.isUrl !== undefined) {
|
if (config.isUrl !== undefined) {
|
||||||
newState.enteredIdentityServerUrl = config.isUrl;
|
newState.enteredIdentityServerUrl = config.isUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.props.onServerConfigChange(config);
|
||||||
this.setState(newState, function() {
|
this.setState(newState, function() {
|
||||||
self._initLoginLogic(config.hsUrl || null, config.isUrl);
|
self._initLoginLogic(config.hsUrl || null, config.isUrl);
|
||||||
});
|
});
|
||||||
|
|
|
@ -61,6 +61,7 @@ module.exports = React.createClass({
|
||||||
// registration shouldn't know or care how login is done.
|
// registration shouldn't know or care how login is done.
|
||||||
onLoginClick: PropTypes.func.isRequired,
|
onLoginClick: PropTypes.func.isRequired,
|
||||||
onCancelClick: PropTypes.func,
|
onCancelClick: PropTypes.func,
|
||||||
|
onServerConfigChange: PropTypes.func.isRequired,
|
||||||
},
|
},
|
||||||
|
|
||||||
getInitialState: function() {
|
getInitialState: function() {
|
||||||
|
@ -131,6 +132,7 @@ module.exports = React.createClass({
|
||||||
if (config.isUrl !== undefined) {
|
if (config.isUrl !== undefined) {
|
||||||
newState.isUrl = config.isUrl;
|
newState.isUrl = config.isUrl;
|
||||||
}
|
}
|
||||||
|
this.props.onServerConfigChange(config);
|
||||||
this.setState(newState, function() {
|
this.setState(newState, function() {
|
||||||
this._replaceClient();
|
this._replaceClient();
|
||||||
});
|
});
|
||||||
|
|
|
@ -17,9 +17,12 @@ limitations under the License.
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
|
import { MatrixClient } from 'matrix-js-sdk';
|
||||||
|
|
||||||
import { KeyCode } from '../../../Keyboard';
|
import { KeyCode } from '../../../Keyboard';
|
||||||
import AccessibleButton from '../elements/AccessibleButton';
|
import AccessibleButton from '../elements/AccessibleButton';
|
||||||
import sdk from '../../../index';
|
import sdk from '../../../index';
|
||||||
|
import MatrixClientPeg from '../../../MatrixClientPeg';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Basic container for modal dialogs.
|
* Basic container for modal dialogs.
|
||||||
|
@ -51,6 +54,20 @@ export default React.createClass({
|
||||||
children: PropTypes.node,
|
children: PropTypes.node,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
childContextTypes: {
|
||||||
|
matrixClient: PropTypes.instanceOf(MatrixClient),
|
||||||
|
},
|
||||||
|
|
||||||
|
getChildContext: function() {
|
||||||
|
return {
|
||||||
|
matrixClient: this._matrixClient,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
componentWillMount() {
|
||||||
|
this._matrixClient = MatrixClientPeg.get();
|
||||||
|
},
|
||||||
|
|
||||||
_onKeyDown: function(e) {
|
_onKeyDown: function(e) {
|
||||||
if (this.props.onKeyDown) {
|
if (this.props.onKeyDown) {
|
||||||
this.props.onKeyDown(e);
|
this.props.onKeyDown(e);
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2015, 2016 OpenMarket Ltd
|
Copyright 2015, 2016 OpenMarket Ltd
|
||||||
|
Copyright 2018 New Vector Ltd
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
@ -18,6 +19,7 @@ import PropTypes from 'prop-types';
|
||||||
import MatrixClientPeg from '../../../MatrixClientPeg';
|
import MatrixClientPeg from '../../../MatrixClientPeg';
|
||||||
import dis from '../../../dispatcher';
|
import dis from '../../../dispatcher';
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
|
import sdk from '../../../index';
|
||||||
|
|
||||||
module.exports = React.createClass({
|
module.exports = React.createClass({
|
||||||
displayName: 'IncomingCallBox',
|
displayName: 'IncomingCallBox',
|
||||||
|
@ -26,14 +28,16 @@ module.exports = React.createClass({
|
||||||
incomingCall: PropTypes.object,
|
incomingCall: PropTypes.object,
|
||||||
},
|
},
|
||||||
|
|
||||||
onAnswerClick: function() {
|
onAnswerClick: function(e) {
|
||||||
|
e.stopPropagation();
|
||||||
dis.dispatch({
|
dis.dispatch({
|
||||||
action: 'answer',
|
action: 'answer',
|
||||||
room_id: this.props.incomingCall.roomId,
|
room_id: this.props.incomingCall.roomId,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
onRejectClick: function() {
|
onRejectClick: function(e) {
|
||||||
|
e.stopPropagation();
|
||||||
dis.dispatch({
|
dis.dispatch({
|
||||||
action: 'hangup',
|
action: 'hangup',
|
||||||
room_id: this.props.incomingCall.roomId,
|
room_id: this.props.incomingCall.roomId,
|
||||||
|
@ -59,6 +63,7 @@ module.exports = React.createClass({
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
|
||||||
return (
|
return (
|
||||||
<div className="mx_IncomingCallBox" id="incomingCallBox">
|
<div className="mx_IncomingCallBox" id="incomingCallBox">
|
||||||
<img className="mx_IncomingCallBox_chevron" src="img/chevron-left.png" width="9" height="16" />
|
<img className="mx_IncomingCallBox_chevron" src="img/chevron-left.png" width="9" height="16" />
|
||||||
|
@ -67,14 +72,14 @@ module.exports = React.createClass({
|
||||||
</div>
|
</div>
|
||||||
<div className="mx_IncomingCallBox_buttons">
|
<div className="mx_IncomingCallBox_buttons">
|
||||||
<div className="mx_IncomingCallBox_buttons_cell">
|
<div className="mx_IncomingCallBox_buttons_cell">
|
||||||
<div className="mx_IncomingCallBox_buttons_decline" onClick={this.onRejectClick}>
|
<AccessibleButton className="mx_IncomingCallBox_buttons_decline" onClick={this.onRejectClick}>
|
||||||
{ _t("Decline") }
|
{ _t("Decline") }
|
||||||
</div>
|
</AccessibleButton>
|
||||||
</div>
|
</div>
|
||||||
<div className="mx_IncomingCallBox_buttons_cell">
|
<div className="mx_IncomingCallBox_buttons_cell">
|
||||||
<div className="mx_IncomingCallBox_buttons_accept" onClick={this.onAnswerClick}>
|
<AccessibleButton className="mx_IncomingCallBox_buttons_accept" onClick={this.onAnswerClick}>
|
||||||
{ _t("Accept") }
|
{ _t("Accept") }
|
||||||
</div>
|
</AccessibleButton>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -35,13 +35,17 @@ module.exports = function(f, minIntervalMs) {
|
||||||
|
|
||||||
if (self.lastCall < now - minIntervalMs) {
|
if (self.lastCall < now - minIntervalMs) {
|
||||||
f.apply(this);
|
f.apply(this);
|
||||||
self.lastCall = now;
|
// get the time again now the function has finished, so if it
|
||||||
|
// took longer than the delay time to execute, it doesn't
|
||||||
|
// immediately become eligible to run again.
|
||||||
|
self.lastCall = Date.now();
|
||||||
} else if (self.scheduledCall === undefined) {
|
} else if (self.scheduledCall === undefined) {
|
||||||
self.scheduledCall = setTimeout(
|
self.scheduledCall = setTimeout(
|
||||||
() => {
|
() => {
|
||||||
self.scheduledCall = undefined;
|
self.scheduledCall = undefined;
|
||||||
f.apply(this);
|
f.apply(this);
|
||||||
self.lastCall = now;
|
// get time again as per above
|
||||||
|
self.lastCall = Date.now();
|
||||||
},
|
},
|
||||||
(self.lastCall + minIntervalMs) - now,
|
(self.lastCall + minIntervalMs) - now,
|
||||||
);
|
);
|
||||||
|
|
Loading…
Reference in a new issue