Merge remote-tracking branch 'origin/develop' into dbkr/udd_no_auto_show

This commit is contained in:
David Baker 2017-11-13 17:45:57 +00:00
commit 0659ac1ccb
28 changed files with 2124 additions and 239 deletions

View file

@ -15,6 +15,7 @@ limitations under the License.
*/
import React from 'react';
import { MatrixClient } from 'matrix-js-sdk';
import sdk from '../../../index';
import { _t } from '../../../languageHandler';
import classnames from 'classnames';
@ -35,6 +36,8 @@ export default React.createClass({
member: React.PropTypes.object,
// group member object. Supply either this or 'member'
groupMember: GroupMemberType,
// needed if a group member is specified
matrixClient: React.PropTypes.instanceOf(MatrixClient),
action: React.PropTypes.string.isRequired, // eg. 'Ban'
title: React.PropTypes.string.isRequired, // eg. 'Ban this user?'
@ -104,10 +107,11 @@ export default React.createClass({
name = this.props.member.name;
userId = this.props.member.userId;
} else {
// we don't get this info from the API yet
avatar = <BaseAvatar name={this.props.groupMember.userId} width={48} height={48} />;
name = this.props.groupMember.userId;
const httpAvatarUrl = this.props.groupMember.avatarUrl ?
this.props.matrixClient.mxcUrlToHttp(this.props.groupMember.avatarUrl, 48, 48) : null;
name = this.props.groupMember.displayname || this.props.groupMember.userId;
userId = this.props.groupMember.userId;
avatar = <BaseAvatar name={name} url={httpAvatarUrl} width={48} height={48} />;
}
return (

View file

@ -17,6 +17,7 @@ limitations under the License.
'use strict';
import url from 'url';
import qs from 'querystring';
import React from 'react';
import MatrixClientPeg from '../../../MatrixClientPeg';
import PlatformPeg from '../../../PlatformPeg';
@ -51,42 +52,63 @@ export default React.createClass({
creatorUserId: React.PropTypes.string,
},
getDefaultProps: function() {
getDefaultProps() {
return {
url: "",
};
},
getInitialState: function() {
const widgetPermissionId = [this.props.room.roomId, encodeURIComponent(this.props.url)].join('_');
/**
* Set initial component state when the App wUrl (widget URL) is being updated.
* Component props *must* be passed (rather than relying on this.props).
* @param {Object} newProps The new properties of the component
* @return {Object} Updated component state to be set with setState
*/
_getNewState(newProps) {
const widgetPermissionId = [newProps.room.roomId, encodeURIComponent(newProps.url)].join('_');
const hasPermissionToLoad = localStorage.getItem(widgetPermissionId);
return {
loading: false,
widgetUrl: this.props.url,
initialising: true, // True while we are mangling the widget URL
loading: true, // True while the iframe content is loading
widgetUrl: newProps.url,
widgetPermissionId: widgetPermissionId,
// Assume that widget has permission to load if we are the user who added it to the room, or if explicitly granted by the user
hasPermissionToLoad: hasPermissionToLoad === 'true' || this.props.userId === this.props.creatorUserId,
// Assume that widget has permission to load if we are the user who
// added it to the room, or if explicitly granted by the user
hasPermissionToLoad: hasPermissionToLoad === 'true' || newProps.userId === newProps.creatorUserId,
error: null,
deleting: false,
};
},
// Returns true if props.url is a scalar URL, typically https://scalar.vector.im/api
isScalarUrl: function() {
getInitialState() {
return this._getNewState(this.props);
},
/**
* Returns true if specified url is a scalar URL, typically https://scalar.vector.im/api
* @param {[type]} url URL to check
* @return {Boolean} True if specified URL is a scalar URL
*/
isScalarUrl(url) {
if (!url) {
console.error('Scalar URL check failed. No URL specified');
return false;
}
let scalarUrls = SdkConfig.get().integrations_widgets_urls;
if (!scalarUrls || scalarUrls.length == 0) {
scalarUrls = [SdkConfig.get().integrations_rest_url];
}
for (let i = 0; i < scalarUrls.length; i++) {
if (this.props.url.startsWith(scalarUrls[i])) {
if (url.startsWith(scalarUrls[i])) {
return true;
}
}
return false;
},
isMixedContent: function() {
isMixedContent() {
const parentContentProtocol = window.location.protocol;
const u = url.parse(this.props.url);
const childContentProtocol = u.protocol;
@ -98,43 +120,73 @@ export default React.createClass({
return false;
},
componentWillMount: function() {
if (!this.isScalarUrl()) {
componentWillMount() {
window.addEventListener('message', this._onMessage, false);
this.setScalarToken();
},
/**
* Adds a scalar token to the widget URL, if required
* Component initialisation is only complete when this function has resolved
*/
setScalarToken() {
this.setState({initialising: true});
if (!this.isScalarUrl(this.props.url)) {
console.warn('Non-scalar widget, not setting scalar token!', url);
this.setState({
error: null,
widgetUrl: this.props.url,
initialising: false,
});
return;
}
// Fetch the token before loading the iframe as we need to mangle the URL
this.setState({
loading: true,
});
this._scalarClient = new ScalarAuthClient();
// Fetch the token before loading the iframe as we need it to mangle the URL
if (!this._scalarClient) {
this._scalarClient = new ScalarAuthClient();
}
this._scalarClient.getScalarToken().done((token) => {
// Append scalar_token as a query param
// Append scalar_token as a query param if not already present
this._scalarClient.scalarToken = token;
const u = url.parse(this.props.url);
if (!u.search) {
u.search = "?scalar_token=" + encodeURIComponent(token);
} else {
u.search += "&scalar_token=" + encodeURIComponent(token);
const params = qs.parse(u.query);
if (!params.scalar_token) {
params.scalar_token = encodeURIComponent(token);
// u.search must be set to undefined, so that u.format() uses query paramerters - https://nodejs.org/docs/latest/api/url.html#url_url_format_url_options
u.search = undefined;
u.query = params;
}
this.setState({
error: null,
widgetUrl: u.format(),
loading: false,
initialising: false,
});
}, (err) => {
console.error("Failed to get scalar_token", err);
this.setState({
error: err.message,
loading: false,
initialising: false,
});
});
window.addEventListener('message', this._onMessage, false);
},
componentWillUnmount() {
window.removeEventListener('message', this._onMessage);
},
componentWillReceiveProps(nextProps) {
if (nextProps.url !== this.props.url) {
this._getNewState(nextProps);
this.setScalarToken();
} else if (nextProps.show && !this.props.show) {
this.setState({
loading: true,
});
}
},
_onMessage(event) {
if (this.props.type !== 'jitsi') {
return;
@ -154,11 +206,11 @@ export default React.createClass({
}
},
_canUserModify: function() {
_canUserModify() {
return WidgetUtils.canUserModifyWidgets(this.props.room.roomId);
},
_onEditClick: function(e) {
_onEditClick(e) {
console.log("Edit widget ID ", this.props.id);
const IntegrationsManager = sdk.getComponent("views.settings.IntegrationsManager");
const src = this._scalarClient.getScalarInterfaceUrlForRoom(
@ -168,9 +220,10 @@ export default React.createClass({
}, "mx_IntegrationsManager");
},
/* If user has permission to modify widgets, delete the widget, otherwise revoke access for the widget to load in the user's browser
/* If user has permission to modify widgets, delete the widget,
* otherwise revoke access for the widget to load in the user's browser
*/
_onDeleteClick: function() {
_onDeleteClick() {
if (this._canUserModify()) {
// Show delete confirmation dialog
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
@ -202,6 +255,10 @@ export default React.createClass({
}
},
_onLoaded() {
this.setState({loading: false});
},
// Widget labels to render, depending upon user permissions
// These strings are translated at the point that they are inserted in to the DOM, in the render method
_deleteWidgetLabel() {
@ -224,7 +281,7 @@ export default React.createClass({
this.setState({hasPermissionToLoad: false});
},
formatAppTileName: function() {
formatAppTileName() {
let appTileName = "No name";
if(this.props.name && this.props.name.trim()) {
appTileName = this.props.name.trim();
@ -232,7 +289,7 @@ export default React.createClass({
return appTileName;
},
onClickMenuBar: function(ev) {
onClickMenuBar(ev) {
ev.preventDefault();
// Ignore clicks on menu bar children
@ -247,7 +304,7 @@ export default React.createClass({
});
},
render: function() {
render() {
let appTileBody;
// Don't render widget if it is in the process of being deleted
@ -269,29 +326,30 @@ export default React.createClass({
}
if (this.props.show) {
if (this.state.loading) {
appTileBody = (
<div className='mx_AppTileBody mx_AppLoading'>
<MessageSpinner msg='Loading...' />
</div>
);
const loadingElement = (
<div className='mx_AppTileBody mx_AppLoading'>
<MessageSpinner msg='Loading...' />
</div>
);
if (this.state.initialising) {
appTileBody = loadingElement;
} else if (this.state.hasPermissionToLoad == true) {
if (this.isMixedContent()) {
appTileBody = (
<div className="mx_AppTileBody">
<AppWarning
errorMsg="Error - Mixed content"
/>
<AppWarning errorMsg="Error - Mixed content" />
</div>
);
} else {
appTileBody = (
<div className="mx_AppTileBody">
<div className={this.state.loading ? 'mx_AppTileBody mx_AppLoading' : 'mx_AppTileBody'}>
{ this.state.loading && loadingElement }
<iframe
ref="appFrame"
src={safeWidgetUrl}
allowFullScreen="true"
sandbox={sandboxFlags}
onLoad={this._onLoaded}
></iframe>
</div>
);

View file

@ -19,7 +19,6 @@
import React from 'react';
import PropTypes from 'prop-types';
import {MatrixClient} from 'matrix-js-sdk';
import UserSettingsStore from '../../../UserSettingsStore';
import FlairStore from '../../../stores/FlairStore';
import dis from '../../../dispatcher';
@ -43,18 +42,22 @@ class FlairAvatar extends React.Component {
render() {
const httpUrl = this.context.matrixClient.mxcUrlToHttp(
this.props.groupProfile.avatarUrl, 16, 16, 'scale', false);
const tooltip = this.props.groupProfile.name ?
`${this.props.groupProfile.name} (${this.props.groupProfile.groupId})`:
this.props.groupProfile.groupId;
return <img
src={httpUrl}
width="16"
height="16"
onClick={this.onClick}
title={this.props.groupProfile.groupId} />;
title={tooltip} />;
}
}
FlairAvatar.propTypes = {
groupProfile: PropTypes.shape({
groupId: PropTypes.string.isRequired,
name: PropTypes.string,
avatarUrl: PropTypes.string.isRequired,
}),
};
@ -79,9 +82,7 @@ export default class Flair extends React.Component {
componentWillMount() {
this._unmounted = false;
if (UserSettingsStore.isFeatureEnabled('feature_groups') && FlairStore.groupSupport()) {
this._generateAvatars();
}
this._generateAvatars();
this.context.matrixClient.on('RoomState.events', this.onRoomStateEvents);
}

View file

@ -14,6 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import sdk from '../../../index';
import PropTypes from 'prop-types';
import { _t } from '../../../languageHandler';

View file

@ -16,6 +16,7 @@ limitations under the License.
import React from 'react';
import PropTypes from 'prop-types';
import { MatrixClient } from 'matrix-js-sdk';
import sdk from '../../../index';
import dis from '../../../dispatcher';
import AccessibleButton from '../elements/AccessibleButton';
@ -27,6 +28,10 @@ export default React.createClass({
group: PropTypes.object.isRequired,
},
contextTypes: {
matrixClient: PropTypes.instanceOf(MatrixClient),
},
onClick: function(e) {
dis.dispatch({
action: 'view_group',
@ -39,13 +44,15 @@ export default React.createClass({
const EmojiText = sdk.getComponent('elements.EmojiText');
const groupName = this.props.group.name || this.props.group.groupId;
const httpAvatarUrl = this.props.group.avatarUrl ?
this.context.matrixClient.mxcUrlToHttp(this.props.group.avatarUrl, 24, 24) : null;
const av = <BaseAvatar name={groupName} width={24} height={24} url={this.props.group.avatarUrl} />;
const av = <BaseAvatar name={groupName} width={24} height={24} url={httpAvatarUrl} />;
const label = <EmojiText
element="div"
title={this.props.group.groupId}
className="mx_RoomTile_name"
className="mx_RoomTile_name mx_RoomTile_badgeShown"
dir="auto"
>
{ groupName }

View file

@ -83,6 +83,7 @@ module.exports = React.createClass({
_onKick: function() {
const ConfirmUserActionDialog = sdk.getComponent("dialogs.ConfirmUserActionDialog");
Modal.createDialog(ConfirmUserActionDialog, {
matrixClient: this.context.matrixClient,
groupMember: this.props.groupMember,
action: this.state.isUserInvited ? _t('Disinvite') : _t('Remove from community'),
title: this.state.isUserInvited ? _t('Disinvite this user from community?')

View file

@ -137,7 +137,7 @@ module.exports = React.createClass({
const groupId = this.props.groupId;
const roomId = this.props.groupRoomId;
const roomName = this.state.groupRoom.displayname;
this._groupStore.updateGroupRoomAssociation(roomId, isPublic).catch((err) => {
this._groupStore.updateGroupRoomVisibility(roomId, isPublic).catch((err) => {
console.error(`Error whilst changing visibility of ${roomId} in ${groupId} to ${isPublic}`, err);
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createTrackedDialog('Failed to remove room from group', '', ErrorDialog, {

View file

@ -224,7 +224,7 @@ module.exports = React.createClass({
if (roomNotifTextNodes.length > 0) {
const pushProcessor = new PushProcessor(MatrixClientPeg.get());
const atRoomRule = pushProcessor.getPushRuleById(".m.rule.roomnotif");
if (pushProcessor.ruleMatchesEvent(atRoomRule, this.props.mxEvent)) {
if (atRoomRule && pushProcessor.ruleMatchesEvent(atRoomRule, this.props.mxEvent)) {
// Now replace all those nodes with Pills
for (const roomNotifTextNode of roomNotifTextNodes) {
const pillContainer = document.createElement('span');

View file

@ -671,13 +671,11 @@ module.exports = React.createClass({
const self = this;
let relatedGroupsSection;
if (UserSettingsStore.isFeatureEnabled('feature_groups')) {
relatedGroupsSection = <RelatedGroupSettings ref="related_groups"
roomId={this.props.room.roomId}
canSetRelatedGroups={roomState.mayClientSendStateEvent("m.room.related_groups", cli)}
relatedGroupsEvent={this.props.room.currentState.getStateEvents('m.room.related_groups', '')} />;
}
const relatedGroupsSection = <RelatedGroupSettings ref="related_groups"
roomId={this.props.room.roomId}
canSetRelatedGroups={roomState.mayClientSendStateEvent("m.room.related_groups", cli)}
relatedGroupsEvent={this.props.room.currentState.getStateEvents('m.room.related_groups', '')}
/>;
let userLevelsSection;
if (Object.keys(user_levels).length) {