Merge branch 'develop' into travis/granular-settings
This commit is contained in:
commit
c5c346f0b7
28 changed files with 2124 additions and 235 deletions
|
@ -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 (
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
|
|
|
@ -43,18 +43,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 +83,7 @@ export default class Flair extends React.Component {
|
|||
|
||||
componentWillMount() {
|
||||
this._unmounted = false;
|
||||
if (SettingsStore.isFeatureEnabled('feature_groups') && FlairStore.groupSupport()) {
|
||||
this._generateAvatars();
|
||||
}
|
||||
this._generateAvatars();
|
||||
this.context.matrixClient.on('RoomState.events', this.onRoomStateEvents);
|
||||
}
|
||||
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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 }
|
||||
|
|
|
@ -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?')
|
||||
|
|
|
@ -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, {
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -662,13 +662,11 @@ module.exports = React.createClass({
|
|||
|
||||
const self = this;
|
||||
|
||||
let relatedGroupsSection;
|
||||
if (SettingsStore.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) {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue