Merge remote-tracking branch 'origin/develop' into matthew/status
This commit is contained in:
commit
a0cdaf29f9
30 changed files with 2153 additions and 294 deletions
|
@ -435,14 +435,15 @@ export default React.createClass({
|
|||
},
|
||||
|
||||
componentWillMount: function() {
|
||||
this._matrixClient = MatrixClientPeg.get();
|
||||
this._matrixClient.on("Group.myMembership", this._onGroupMyMembership);
|
||||
|
||||
this._changeAvatarComponent = null;
|
||||
this._initGroupStore(this.props.groupId, true);
|
||||
|
||||
MatrixClientPeg.get().on("Group.myMembership", this._onGroupMyMembership);
|
||||
},
|
||||
|
||||
componentWillUnmount: function() {
|
||||
MatrixClientPeg.get().removeListener("Group.myMembership", this._onGroupMyMembership);
|
||||
this._matrixClient.removeListener("Group.myMembership", this._onGroupMyMembership);
|
||||
this._groupStore.removeAllListeners();
|
||||
},
|
||||
|
||||
|
@ -464,11 +465,11 @@ export default React.createClass({
|
|||
},
|
||||
|
||||
_initGroupStore: function(groupId, firstInit) {
|
||||
const group = MatrixClientPeg.get().getGroup(groupId);
|
||||
const group = this._matrixClient.getGroup(groupId);
|
||||
if (group && group.inviter && group.inviter.userId) {
|
||||
this._fetchInviterProfile(group.inviter.userId);
|
||||
}
|
||||
this._groupStore = GroupStoreCache.getGroupStore(MatrixClientPeg.get(), groupId);
|
||||
this._groupStore = GroupStoreCache.getGroupStore(this._matrixClient, groupId);
|
||||
this._groupStore.registerListener(() => {
|
||||
const summary = this._groupStore.getSummary();
|
||||
if (summary.profile) {
|
||||
|
@ -486,7 +487,7 @@ export default React.createClass({
|
|||
groupRooms: this._groupStore.getGroupRooms(),
|
||||
groupRoomsLoading: !this._groupStore.isStateReady(GroupStore.STATE_KEY.GroupRooms),
|
||||
isUserMember: this._groupStore.getGroupMembers().some(
|
||||
(m) => m.userId === MatrixClientPeg.get().credentials.userId,
|
||||
(m) => m.userId === this._matrixClient.credentials.userId,
|
||||
),
|
||||
error: null,
|
||||
});
|
||||
|
@ -506,7 +507,7 @@ export default React.createClass({
|
|||
this.setState({
|
||||
inviterProfileBusy: true,
|
||||
});
|
||||
MatrixClientPeg.get().getProfileInfo(userId).then((resp) => {
|
||||
this._matrixClient.getProfileInfo(userId).then((resp) => {
|
||||
this.setState({
|
||||
inviterProfile: {
|
||||
avatarUrl: resp.avatar_url,
|
||||
|
@ -571,7 +572,7 @@ export default React.createClass({
|
|||
if (!file) return;
|
||||
|
||||
this.setState({uploadingAvatar: true});
|
||||
MatrixClientPeg.get().uploadContent(file).then((url) => {
|
||||
this._matrixClient.uploadContent(file).then((url) => {
|
||||
const newProfileForm = Object.assign(this.state.profileForm, { avatar_url: url });
|
||||
this.setState({
|
||||
uploadingAvatar: false,
|
||||
|
@ -591,7 +592,7 @@ export default React.createClass({
|
|||
_onSaveClick: function() {
|
||||
this.setState({saving: true});
|
||||
const savePromise = this.state.isUserPrivileged ?
|
||||
MatrixClientPeg.get().setGroupProfile(this.props.groupId, this.state.profileForm) :
|
||||
this._matrixClient.setGroupProfile(this.props.groupId, this.state.profileForm) :
|
||||
Promise.resolve();
|
||||
savePromise.then((result) => {
|
||||
this.setState({
|
||||
|
@ -630,7 +631,7 @@ export default React.createClass({
|
|||
|
||||
_onRejectInviteClick: function() {
|
||||
this.setState({membershipBusy: true});
|
||||
MatrixClientPeg.get().leaveGroup(this.props.groupId).then(() => {
|
||||
this._matrixClient.leaveGroup(this.props.groupId).then(() => {
|
||||
// don't reset membershipBusy here: wait for the membership change to come down the sync
|
||||
}).catch((e) => {
|
||||
this.setState({membershipBusy: false});
|
||||
|
@ -653,7 +654,7 @@ export default React.createClass({
|
|||
if (!confirmed) return;
|
||||
|
||||
this.setState({membershipBusy: true});
|
||||
MatrixClientPeg.get().leaveGroup(this.props.groupId).then(() => {
|
||||
this._matrixClient.leaveGroup(this.props.groupId).then(() => {
|
||||
// don't reset membershipBusy here: wait for the membership change to come down the sync
|
||||
}).catch((e) => {
|
||||
this.setState({membershipBusy: false});
|
||||
|
@ -829,7 +830,7 @@ export default React.createClass({
|
|||
const Spinner = sdk.getComponent("elements.Spinner");
|
||||
const BaseAvatar = sdk.getComponent("avatars.BaseAvatar");
|
||||
|
||||
const group = MatrixClientPeg.get().getGroup(this.props.groupId);
|
||||
const group = this._matrixClient.getGroup(this.props.groupId);
|
||||
if (!group) return null;
|
||||
|
||||
if (group.myMembership === 'invite') {
|
||||
|
@ -839,7 +840,7 @@ export default React.createClass({
|
|||
</div>;
|
||||
}
|
||||
const httpInviterAvatar = this.state.inviterProfile ?
|
||||
MatrixClientPeg.get().mxcUrlToHttp(
|
||||
this._matrixClient.mxcUrlToHttp(
|
||||
this.state.inviterProfile.avatarUrl, 36, 36,
|
||||
) : null;
|
||||
|
||||
|
|
|
@ -74,6 +74,17 @@ const VIEWS = {
|
|||
LOGGED_IN: 6,
|
||||
};
|
||||
|
||||
// Actions that are redirected through the onboarding process prior to being
|
||||
// re-dispatched. NOTE: some actions are non-trivial and would require
|
||||
// re-factoring to be included in this list in future.
|
||||
const ONBOARDING_FLOW_STARTERS = [
|
||||
'view_user_settings',
|
||||
'view_create_chat',
|
||||
'view_create_room',
|
||||
'view_my_groups',
|
||||
'view_group',
|
||||
];
|
||||
|
||||
module.exports = React.createClass({
|
||||
// we export this so that the integration tests can use it :-S
|
||||
statics: {
|
||||
|
@ -377,6 +388,22 @@ module.exports = React.createClass({
|
|||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
|
||||
|
||||
// Start the onboarding process for certain actions
|
||||
if (MatrixClientPeg.get() && MatrixClientPeg.get().isGuest() &&
|
||||
ONBOARDING_FLOW_STARTERS.includes(payload.action)
|
||||
) {
|
||||
// This will cause `payload` to be dispatched later, once a
|
||||
// sync has reached the "prepared" state. Setting a matrix ID
|
||||
// will cause a full login and sync and finally the deferred
|
||||
// action will be dispatched.
|
||||
dis.dispatch({
|
||||
action: 'do_after_sync_prepared',
|
||||
deferred_action: payload,
|
||||
});
|
||||
dis.dispatch({action: 'view_set_mxid'});
|
||||
return;
|
||||
}
|
||||
|
||||
switch (payload.action) {
|
||||
case 'logout':
|
||||
Lifecycle.logout();
|
||||
|
@ -466,16 +493,6 @@ module.exports = React.createClass({
|
|||
this._viewIndexedRoom(payload.roomIndex);
|
||||
break;
|
||||
case 'view_user_settings':
|
||||
if (MatrixClientPeg.get().isGuest()) {
|
||||
dis.dispatch({
|
||||
action: 'do_after_sync_prepared',
|
||||
deferred_action: {
|
||||
action: 'view_user_settings',
|
||||
},
|
||||
});
|
||||
dis.dispatch({action: 'view_set_mxid'});
|
||||
break;
|
||||
}
|
||||
this._setPage(PageTypes.UserSettings);
|
||||
this.notifyNewScreen('settings');
|
||||
break;
|
||||
|
@ -512,7 +529,7 @@ module.exports = React.createClass({
|
|||
this._chatCreateOrReuse(payload.user_id, payload.go_home_on_cancel);
|
||||
break;
|
||||
case 'view_create_chat':
|
||||
this._createChat();
|
||||
showStartChatInviteDialog();
|
||||
break;
|
||||
case 'view_invite':
|
||||
showRoomInviteDialog(payload.roomId);
|
||||
|
@ -753,31 +770,7 @@ module.exports = React.createClass({
|
|||
}).close;
|
||||
},
|
||||
|
||||
_createChat: function() {
|
||||
if (MatrixClientPeg.get().isGuest()) {
|
||||
dis.dispatch({
|
||||
action: 'do_after_sync_prepared',
|
||||
deferred_action: {
|
||||
action: 'view_create_chat',
|
||||
},
|
||||
});
|
||||
dis.dispatch({action: 'view_set_mxid'});
|
||||
return;
|
||||
}
|
||||
showStartChatInviteDialog();
|
||||
},
|
||||
|
||||
_createRoom: function() {
|
||||
if (MatrixClientPeg.get().isGuest()) {
|
||||
dis.dispatch({
|
||||
action: 'do_after_sync_prepared',
|
||||
deferred_action: {
|
||||
action: 'view_create_room',
|
||||
},
|
||||
});
|
||||
dis.dispatch({action: 'view_set_mxid'});
|
||||
return;
|
||||
}
|
||||
const CreateRoomDialog = sdk.getComponent('dialogs.CreateRoomDialog');
|
||||
Modal.createTrackedDialog('Create Room', '', CreateRoomDialog, {
|
||||
onFinished: (shouldCreate, name, noFederate) => {
|
||||
|
|
|
@ -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 (
|
||||
|
|
|
@ -48,9 +48,8 @@ function UserUnknownDeviceList(props) {
|
|||
const {userId, userDevices} = props;
|
||||
|
||||
const deviceListEntries = Object.keys(userDevices).map((deviceId) =>
|
||||
<DeviceListEntry key={deviceId} userId={userId}
|
||||
device={userDevices[deviceId]}
|
||||
/>,
|
||||
<DeviceListEntry key={deviceId} userId={userId}
|
||||
device={userDevices[deviceId]} />,
|
||||
);
|
||||
|
||||
return (
|
||||
|
@ -93,60 +92,26 @@ export default React.createClass({
|
|||
propTypes: {
|
||||
room: React.PropTypes.object.isRequired,
|
||||
|
||||
// map from userid -> deviceid -> deviceinfo
|
||||
devices: React.PropTypes.object.isRequired,
|
||||
onFinished: React.PropTypes.func.isRequired,
|
||||
},
|
||||
|
||||
componentWillMount: function() {
|
||||
this._unmounted = false;
|
||||
|
||||
const roomMembers = this.props.room.getJoinedMembers().map((m) => {
|
||||
return m.userId;
|
||||
});
|
||||
|
||||
this.setState({
|
||||
// map from userid -> deviceid -> deviceinfo
|
||||
devices: null,
|
||||
});
|
||||
MatrixClientPeg.get().downloadKeys(roomMembers, false).then((devices) => {
|
||||
if (this._unmounted) return;
|
||||
|
||||
const unknownDevices = {};
|
||||
// This is all devices in this room, so find the unknown ones.
|
||||
Object.keys(devices).forEach((userId) => {
|
||||
Object.keys(devices[userId]).map((deviceId) => {
|
||||
const device = devices[userId][deviceId];
|
||||
|
||||
if (device.isUnverified() && !device.isKnown()) {
|
||||
if (unknownDevices[userId] === undefined) {
|
||||
unknownDevices[userId] = {};
|
||||
}
|
||||
unknownDevices[userId][deviceId] = device;
|
||||
}
|
||||
|
||||
// Given we've now shown the user the unknown device, it is no longer
|
||||
// unknown to them. Therefore mark it as 'known'.
|
||||
if (!device.isKnown()) {
|
||||
MatrixClientPeg.get().setDeviceKnown(userId, deviceId, true);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
this.setState({
|
||||
devices: unknownDevices,
|
||||
componentDidMount: function() {
|
||||
// Given we've now shown the user the unknown device, it is no longer
|
||||
// unknown to them. Therefore mark it as 'known'.
|
||||
Object.keys(this.props.devices).forEach((userId) => {
|
||||
Object.keys(this.props.devices[userId]).map((deviceId) => {
|
||||
MatrixClientPeg.get().setDeviceKnown(userId, deviceId, true);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
componentWillUnmount: function() {
|
||||
this._unmounted = true;
|
||||
// XXX: temporary logging to try to diagnose
|
||||
// https://github.com/vector-im/riot-web/issues/3148
|
||||
console.log('Opening UnknownDeviceDialog');
|
||||
},
|
||||
|
||||
render: function() {
|
||||
if (this.state.devices === null) {
|
||||
const Spinner = sdk.getComponent("elements.Spinner");
|
||||
return <Spinner />;
|
||||
}
|
||||
|
||||
const client = MatrixClientPeg.get();
|
||||
const blacklistUnverified = client.getGlobalBlacklistUnverifiedDevices() ||
|
||||
this.props.room.getBlacklistUnverifiedDevices();
|
||||
|
@ -189,7 +154,7 @@ export default React.createClass({
|
|||
{ warning }
|
||||
{ _t("Unknown devices") }:
|
||||
|
||||
<UnknownDeviceList devices={this.state.devices} />
|
||||
<UnknownDeviceList devices={this.props.devices} />
|
||||
</GeminiScrollbar>
|
||||
<div className="mx_Dialog_buttons">
|
||||
<button className="mx_Dialog_primary" autoFocus={true}
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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?')
|
||||
|
|
|
@ -108,14 +108,20 @@ export default withMatrixClient(React.createClass({
|
|||
if (!uniqueMembers[m.userId]) uniqueMembers[m.userId] = m;
|
||||
});
|
||||
memberList = Object.keys(uniqueMembers).map((userId) => uniqueMembers[userId]);
|
||||
// Descending sort on isPrivileged = true = 1 to isPrivileged = false = 0
|
||||
memberList.sort((a, b) => {
|
||||
// TODO: should put admins at the top: we don't yet have that info
|
||||
if (a < b) {
|
||||
return -1;
|
||||
} else if (a > b) {
|
||||
return 1;
|
||||
if (a.isPrivileged === b.isPrivileged) {
|
||||
const aName = a.displayname || a.userId;
|
||||
const bName = b.displayname || b.userId;
|
||||
if (aName < bName) {
|
||||
return -1;
|
||||
} else if (aName > bName) {
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
} else {
|
||||
return 0;
|
||||
return a.isPrivileged ? -1 : 1;
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -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) {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue