Merge branch 'develop' into matthew/status
This commit is contained in:
commit
64d8c81897
9 changed files with 119 additions and 63 deletions
|
@ -19,7 +19,7 @@ import PlatformPeg from './PlatformPeg';
|
|||
import SdkConfig from './SdkConfig';
|
||||
|
||||
function getRedactedUrl() {
|
||||
const redactedHash = window.location.hash.replace(/#\/(room|user)\/(.+)/, "#/$1/<redacted>");
|
||||
const redactedHash = window.location.hash.replace(/#\/(group|room|user)\/(.+)/, "#/$1/<redacted>");
|
||||
// hardcoded url to make piwik happy
|
||||
return 'https://riot.im/app/' + redactedHash;
|
||||
}
|
||||
|
|
|
@ -159,6 +159,7 @@ export default class Login {
|
|||
accessToken: data.access_token,
|
||||
});
|
||||
}).catch((fallback_error) => {
|
||||
console.log("fallback HS login failed", fallback_error);
|
||||
// throw the original error
|
||||
throw originalError;
|
||||
});
|
||||
|
@ -179,6 +180,7 @@ export default class Login {
|
|||
accessToken: data.access_token,
|
||||
});
|
||||
}).catch((fallback_error) => {
|
||||
console.log("Lowercase username login failed", fallback_error);
|
||||
// throw the original error
|
||||
throw originalError;
|
||||
});
|
||||
|
@ -210,6 +212,9 @@ export default class Login {
|
|||
return tryLowercaseUsername(originalLoginError);
|
||||
}
|
||||
throw originalLoginError;
|
||||
}).catch((error) => {
|
||||
console.log("Login failed", error);
|
||||
throw error;
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -407,6 +407,10 @@ export default React.createClass({
|
|||
getInitialState: function() {
|
||||
return {
|
||||
summary: null,
|
||||
isGroupPublicised: null,
|
||||
isUserPrivileged: null,
|
||||
groupRooms: null,
|
||||
groupRoomsLoading: null,
|
||||
error: null,
|
||||
editing: false,
|
||||
saving: false,
|
||||
|
@ -458,8 +462,14 @@ export default React.createClass({
|
|||
}
|
||||
this.setState({
|
||||
summary,
|
||||
summaryLoading: !this._groupStore.isStateReady(GroupStore.STATE_KEY.Summary),
|
||||
isGroupPublicised: this._groupStore.getGroupPublicity(),
|
||||
isUserPrivileged: this._groupStore.isUserPrivileged(),
|
||||
groupRooms: this._groupStore.getGroupRooms(),
|
||||
groupRoomsLoading: !this._groupStore.isStateReady(GroupStore.STATE_KEY.GroupRooms),
|
||||
isUserMember: this._groupStore.getGroupMembers().some(
|
||||
(m) => m.userId === MatrixClientPeg.get().credentials.userId,
|
||||
),
|
||||
error: null,
|
||||
});
|
||||
});
|
||||
|
@ -650,6 +660,7 @@ export default React.createClass({
|
|||
const RoomDetailList = sdk.getComponent('rooms.RoomDetailList');
|
||||
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
|
||||
const TintableSvg = sdk.getComponent('elements.TintableSvg');
|
||||
const Spinner = sdk.getComponent('elements.Spinner');
|
||||
|
||||
const addRoomRow = this.state.editing ?
|
||||
(<AccessibleButton className="mx_GroupView_rooms_header_addRow"
|
||||
|
@ -667,7 +678,10 @@ export default React.createClass({
|
|||
<h3>{ _t('Rooms') }</h3>
|
||||
{ addRoomRow }
|
||||
</div>
|
||||
<RoomDetailList rooms={this._groupStore.getGroupRooms()} />
|
||||
{ this.state.groupRoomsLoading ?
|
||||
<Spinner /> :
|
||||
<RoomDetailList rooms={this.state.groupRooms} />
|
||||
}
|
||||
</div>;
|
||||
},
|
||||
|
||||
|
@ -863,7 +877,7 @@ export default React.createClass({
|
|||
const Spinner = sdk.getComponent("elements.Spinner");
|
||||
const TintableSvg = sdk.getComponent("elements.TintableSvg");
|
||||
|
||||
if (this.state.summary === null && this.state.error === null || this.state.saving) {
|
||||
if (this.state.summaryLoading && this.state.error === null || this.state.saving) {
|
||||
return <Spinner />;
|
||||
} else if (this.state.summary) {
|
||||
const summary = this.state.summary;
|
||||
|
@ -884,6 +898,7 @@ export default React.createClass({
|
|||
} else {
|
||||
const GroupAvatar = sdk.getComponent('avatars.GroupAvatar');
|
||||
avatarImage = <GroupAvatar groupId={this.props.groupId}
|
||||
groupName={this.state.profileForm.name}
|
||||
groupAvatarUrl={this.state.profileForm.avatar_url}
|
||||
width={48} height={48} resizeMethod='crop'
|
||||
/>;
|
||||
|
@ -927,25 +942,28 @@ export default React.createClass({
|
|||
tabIndex="2"
|
||||
dir="auto" />;
|
||||
} else {
|
||||
const onGroupHeaderItemClick = this.state.isUserMember ? this._onEditClick : null;
|
||||
const groupAvatarUrl = summary.profile ? summary.profile.avatar_url : null;
|
||||
const groupName = summary.profile ? summary.profile.name : null;
|
||||
avatarNode = <GroupAvatar
|
||||
groupId={this.props.groupId}
|
||||
groupAvatarUrl={groupAvatarUrl}
|
||||
onClick={this._onEditClick}
|
||||
groupName={groupName}
|
||||
onClick={onGroupHeaderItemClick}
|
||||
width={48} height={48}
|
||||
/>;
|
||||
if (summary.profile && summary.profile.name) {
|
||||
nameNode = <div onClick={this._onEditClick}>
|
||||
nameNode = <div onClick={onGroupHeaderItemClick}>
|
||||
<span>{ summary.profile.name }</span>
|
||||
<span className="mx_GroupView_header_groupid">
|
||||
({ this.props.groupId })
|
||||
</span>
|
||||
</div>;
|
||||
} else {
|
||||
nameNode = <span onClick={this._onEditClick}>{ this.props.groupId }</span>;
|
||||
nameNode = <span onClick={onGroupHeaderItemClick}>{ this.props.groupId }</span>;
|
||||
}
|
||||
if (summary.profile && summary.profile.short_description) {
|
||||
shortDescNode = <span onClick={this._onEditClick}>{ summary.profile.short_description }</span>;
|
||||
shortDescNode = <span onClick={onGroupHeaderItemClick}>{ summary.profile.short_description }</span>;
|
||||
}
|
||||
}
|
||||
if (this.state.editing) {
|
||||
|
@ -986,6 +1004,7 @@ export default React.createClass({
|
|||
const headerClasses = {
|
||||
mx_GroupView_header: true,
|
||||
mx_GroupView_header_view: !this.state.editing,
|
||||
mx_GroupView_header_isUserMember: this.state.isUserMember,
|
||||
};
|
||||
|
||||
return (
|
||||
|
|
|
@ -24,6 +24,7 @@ export default React.createClass({
|
|||
|
||||
propTypes: {
|
||||
groupId: PropTypes.string,
|
||||
groupName: PropTypes.string,
|
||||
groupAvatarUrl: PropTypes.string,
|
||||
width: PropTypes.number,
|
||||
height: PropTypes.number,
|
||||
|
@ -57,7 +58,7 @@ export default React.createClass({
|
|||
|
||||
return (
|
||||
<BaseAvatar
|
||||
name={this.props.groupId[1]}
|
||||
name={this.props.groupName || this.props.groupId[1]}
|
||||
idName={this.props.groupId}
|
||||
url={this.getGroupAvatarUrl()}
|
||||
{...otherProps}
|
||||
|
|
|
@ -55,8 +55,8 @@ export default React.createClass({
|
|||
|
||||
_checkGroupId: function(e) {
|
||||
let error = null;
|
||||
if (!/^[a-zA-Z0-9]*$/.test(this.state.groupId)) {
|
||||
error = _t("Community IDs may only contain alphanumeric characters");
|
||||
if (!/^[a-z0-9=_\-\.\/]*$/.test(this.state.groupId)) {
|
||||
error = _t("Community IDs may only contain characters a-z, 0-9, or '=_-./'");
|
||||
}
|
||||
this.setState({
|
||||
groupIdError: error,
|
||||
|
|
|
@ -17,50 +17,66 @@ limitations under the License.
|
|||
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import { MatrixClient } from 'matrix-js-sdk';
|
||||
import dis from '../../../dispatcher';
|
||||
import Modal from '../../../Modal';
|
||||
import sdk from '../../../index';
|
||||
import { _t } from '../../../languageHandler';
|
||||
import { GroupMemberType } from '../../../groups';
|
||||
import { groupMemberFromApiObject } from '../../../groups';
|
||||
import withMatrixClient from '../../../wrappers/withMatrixClient';
|
||||
import GroupStoreCache from '../../../stores/GroupStoreCache';
|
||||
import AccessibleButton from '../elements/AccessibleButton';
|
||||
import GeminiScrollbar from 'react-gemini-scrollbar';
|
||||
|
||||
|
||||
module.exports = withMatrixClient(React.createClass({
|
||||
module.exports = React.createClass({
|
||||
displayName: 'GroupMemberInfo',
|
||||
|
||||
contextTypes: {
|
||||
matrixClient: PropTypes.instanceOf(MatrixClient),
|
||||
},
|
||||
|
||||
propTypes: {
|
||||
matrixClient: PropTypes.object.isRequired,
|
||||
groupId: PropTypes.string,
|
||||
groupMember: GroupMemberType,
|
||||
isInvited: PropTypes.bool,
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
return {
|
||||
fetching: false,
|
||||
removingUser: false,
|
||||
groupMembers: null,
|
||||
isUserPrivilegedInGroup: null,
|
||||
};
|
||||
},
|
||||
|
||||
componentWillMount: function() {
|
||||
this._fetchMembers();
|
||||
this._initGroupStore(this.props.groupId);
|
||||
},
|
||||
|
||||
_fetchMembers: function() {
|
||||
this.setState({fetching: true});
|
||||
this.props.matrixClient.getGroupUsers(this.props.groupId).then((result) => {
|
||||
this.setState({
|
||||
groupMembers: result.chunk.map((apiMember) => {
|
||||
return groupMemberFromApiObject(apiMember);
|
||||
}),
|
||||
fetching: false,
|
||||
});
|
||||
}).catch((e) => {
|
||||
this.setState({fetching: false});
|
||||
console.error("Failed to get group groupMember list: ", e);
|
||||
componentWillReceiveProps(newProps) {
|
||||
if (newProps.groupId !== this.props.groupId) {
|
||||
this._unregisterGroupStore();
|
||||
this._initGroupStore(newProps.groupId);
|
||||
}
|
||||
},
|
||||
|
||||
_initGroupStore(groupId) {
|
||||
this._groupStore = GroupStoreCache.getGroupStore(
|
||||
this.context.matrixClient, this.props.groupId,
|
||||
);
|
||||
this._groupStore.registerListener(this.onGroupStoreUpdated);
|
||||
},
|
||||
|
||||
_unregisterGroupStore() {
|
||||
if (this._groupStore) {
|
||||
this._groupStore.unregisterListener(this.onGroupStoreUpdated);
|
||||
}
|
||||
},
|
||||
|
||||
onGroupStoreUpdated: function() {
|
||||
this.setState({
|
||||
isUserInvited: this._groupStore.getGroupInvitedMembers().some(
|
||||
(m) => m.userId === this.props.groupMember.userId,
|
||||
),
|
||||
isUserPrivilegedInGroup: this._groupStore.isUserPrivileged(),
|
||||
});
|
||||
},
|
||||
|
||||
|
@ -68,13 +84,13 @@ module.exports = withMatrixClient(React.createClass({
|
|||
const ConfirmUserActionDialog = sdk.getComponent("dialogs.ConfirmUserActionDialog");
|
||||
Modal.createDialog(ConfirmUserActionDialog, {
|
||||
groupMember: this.props.groupMember,
|
||||
action: _t('Remove from community'),
|
||||
action: this.state.isUserInvited ? _t('Disinvite') : _t('Remove from community'),
|
||||
danger: true,
|
||||
onFinished: (proceed) => {
|
||||
if (!proceed) return;
|
||||
|
||||
this.setState({removingUser: true});
|
||||
this.props.matrixClient.removeUserFromGroup(
|
||||
this.context.matrixClient.removeUserFromGroup(
|
||||
this.props.groupId, this.props.groupMember.userId,
|
||||
).then(() => {
|
||||
// return to the user list
|
||||
|
@ -86,7 +102,9 @@ module.exports = withMatrixClient(React.createClass({
|
|||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
Modal.createTrackedDialog('Failed to remove user from group', '', ErrorDialog, {
|
||||
title: _t('Error'),
|
||||
description: _t('Failed to remove user from community'),
|
||||
description: this.state.isUserInvited ?
|
||||
_t('Failed to withdraw invitation') :
|
||||
_t('Failed to remove user from community'),
|
||||
});
|
||||
}).finally(() => {
|
||||
this.setState({removingUser: false});
|
||||
|
@ -111,24 +129,17 @@ module.exports = withMatrixClient(React.createClass({
|
|||
},
|
||||
|
||||
render: function() {
|
||||
if (this.state.fetching || this.state.removingUser) {
|
||||
if (this.state.removingUser) {
|
||||
const Spinner = sdk.getComponent("elements.Spinner");
|
||||
return <Spinner />;
|
||||
}
|
||||
if (!this.state.groupMembers) return null;
|
||||
|
||||
const targetIsInGroup = this.state.groupMembers.some((m) => {
|
||||
return m.userId === this.props.groupMember.userId;
|
||||
});
|
||||
|
||||
let kickButton;
|
||||
let adminButton;
|
||||
|
||||
if (targetIsInGroup) {
|
||||
kickButton = (
|
||||
let adminTools;
|
||||
if (this.state.isUserPrivilegedInGroup) {
|
||||
const kickButton = (
|
||||
<AccessibleButton className="mx_MemberInfo_field"
|
||||
onClick={this._onKick}>
|
||||
{ _t('Remove from community') }
|
||||
{ this.state.isUserInvited ? _t('Disinvite') : _t('Remove from community') }
|
||||
</AccessibleButton>
|
||||
);
|
||||
|
||||
|
@ -137,22 +148,19 @@ module.exports = withMatrixClient(React.createClass({
|
|||
giveModButton = <AccessibleButton className="mx_MemberInfo_field" onClick={this.onModToggle}>
|
||||
{giveOpLabel}
|
||||
</AccessibleButton>;*/
|
||||
|
||||
if (kickButton) {
|
||||
adminTools =
|
||||
<div className="mx_MemberInfo_adminTools">
|
||||
<h3>{ _t("Admin Tools") }</h3>
|
||||
<div className="mx_MemberInfo_buttons">
|
||||
{ kickButton }
|
||||
</div>
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
|
||||
let adminTools;
|
||||
if (kickButton || adminButton) {
|
||||
adminTools =
|
||||
<div className="mx_MemberInfo_adminTools">
|
||||
<h3>{ _t("Admin Tools") }</h3>
|
||||
|
||||
<div className="mx_MemberInfo_buttons">
|
||||
{ kickButton }
|
||||
{ adminButton }
|
||||
</div>
|
||||
</div>;
|
||||
}
|
||||
|
||||
const avatarUrl = this.props.matrixClient.mxcUrlToHttp(
|
||||
const avatarUrl = this.context.matrixClient.mxcUrlToHttp(
|
||||
this.props.groupMember.avatarUrl,
|
||||
36, 36, 'crop',
|
||||
);
|
||||
|
@ -192,4 +200,4 @@ module.exports = withMatrixClient(React.createClass({
|
|||
</div>
|
||||
);
|
||||
},
|
||||
}));
|
||||
});
|
||||
|
|
|
@ -184,7 +184,8 @@ module.exports = React.createClass({
|
|||
});
|
||||
},
|
||||
|
||||
onClickChange: function() {
|
||||
onClickChange: function(ev) {
|
||||
ev.preventDefault();
|
||||
const oldPassword = this.state.cachedPassword || this.refs.old_input.value;
|
||||
const newPassword = this.refs.new_input.value;
|
||||
const confirmPassword = this.refs.confirm_input.value;
|
||||
|
|
|
@ -152,7 +152,6 @@
|
|||
"%(widgetName)s widget removed by %(senderName)s": "%(widgetName)s widget removed by %(senderName)s",
|
||||
"Communities": "Communities",
|
||||
"Message Pinning": "Message Pinning",
|
||||
"Mention": "Mention",
|
||||
"%(displayName)s is typing": "%(displayName)s is typing",
|
||||
"%(names)s and one other are typing": "%(names)s and one other are typing",
|
||||
"%(names)s and %(count)s others are typing|other": "%(names)s and %(count)s others are typing",
|
||||
|
@ -240,6 +239,7 @@
|
|||
"Unignore": "Unignore",
|
||||
"Ignore": "Ignore",
|
||||
"Jump to read receipt": "Jump to read receipt",
|
||||
"Mention": "Mention",
|
||||
"Invite": "Invite",
|
||||
"User Options": "User Options",
|
||||
"Direct chats": "Direct chats",
|
||||
|
@ -489,6 +489,7 @@
|
|||
"Identity server URL": "Identity server URL",
|
||||
"What does this mean?": "What does this mean?",
|
||||
"Remove from community": "Remove from community",
|
||||
"Failed to withdraw invitation": "Failed to withdraw invitation",
|
||||
"Failed to remove user from community": "Failed to remove user from community",
|
||||
"Filter community members": "Filter community members",
|
||||
"Filter community rooms": "Filter community rooms",
|
||||
|
@ -589,7 +590,7 @@
|
|||
"Confirm Removal": "Confirm Removal",
|
||||
"Are you sure you wish to remove (delete) this event? Note that if you delete a room name or topic change, it could undo the change.": "Are you sure you wish to remove (delete) this event? Note that if you delete a room name or topic change, it could undo the change.",
|
||||
"%(actionVerb)s this person?": "%(actionVerb)s this person?",
|
||||
"Community IDs may only contain alphanumeric characters": "Community IDs may only contain alphanumeric characters",
|
||||
"Community IDs may only contain characters a-z, 0-9, or '=_-./'": "Community IDs may only contain characters a-z, 0-9, or '=_-./'",
|
||||
"Something went wrong whilst creating your community": "Something went wrong whilst creating your community",
|
||||
"Create Community": "Create Community",
|
||||
"Community Name": "Community Name",
|
||||
|
|
|
@ -23,12 +23,23 @@ import FlairStore from './FlairStore';
|
|||
* other useful group APIs that may have an effect on the group summary.
|
||||
*/
|
||||
export default class GroupStore extends EventEmitter {
|
||||
|
||||
static STATE_KEY = {
|
||||
GroupMembers: 'GroupMembers',
|
||||
GroupInvitedMembers: 'GroupInvitedMembers',
|
||||
Summary: 'Summary',
|
||||
GroupRooms: 'GroupRooms',
|
||||
};
|
||||
|
||||
constructor(matrixClient, groupId) {
|
||||
super();
|
||||
this.groupId = groupId;
|
||||
this._matrixClient = matrixClient;
|
||||
this._summary = {};
|
||||
this._rooms = [];
|
||||
this._members = [];
|
||||
this._invitedMembers = [];
|
||||
this._ready = {};
|
||||
|
||||
this.on('error', (err) => {
|
||||
console.error(`GroupStore for ${this.groupId} encountered error`, err);
|
||||
|
@ -40,6 +51,7 @@ export default class GroupStore extends EventEmitter {
|
|||
this._members = result.chunk.map((apiMember) => {
|
||||
return groupMemberFromApiObject(apiMember);
|
||||
});
|
||||
this._ready[GroupStore.STATE_KEY.GroupMembers] = true;
|
||||
this._notifyListeners();
|
||||
}).catch((err) => {
|
||||
console.error("Failed to get group member list: " + err);
|
||||
|
@ -50,6 +62,7 @@ export default class GroupStore extends EventEmitter {
|
|||
this._invitedMembers = result.chunk.map((apiMember) => {
|
||||
return groupMemberFromApiObject(apiMember);
|
||||
});
|
||||
this._ready[GroupStore.STATE_KEY.GroupInvitedMembers] = true;
|
||||
this._notifyListeners();
|
||||
}).catch((err) => {
|
||||
// Invited users not visible to non-members
|
||||
|
@ -64,6 +77,7 @@ export default class GroupStore extends EventEmitter {
|
|||
_fetchSummary() {
|
||||
this._matrixClient.getGroupSummary(this.groupId).then((resp) => {
|
||||
this._summary = resp;
|
||||
this._ready[GroupStore.STATE_KEY.Summary] = true;
|
||||
this._notifyListeners();
|
||||
}).catch((err) => {
|
||||
this.emit('error', err);
|
||||
|
@ -75,6 +89,7 @@ export default class GroupStore extends EventEmitter {
|
|||
this._rooms = resp.chunk.map((apiRoom) => {
|
||||
return groupRoomFromApiObject(apiRoom);
|
||||
});
|
||||
this._ready[GroupStore.STATE_KEY.GroupRooms] = true;
|
||||
this._notifyListeners();
|
||||
}).catch((err) => {
|
||||
this.emit('error', err);
|
||||
|
@ -87,6 +102,8 @@ export default class GroupStore extends EventEmitter {
|
|||
|
||||
registerListener(fn) {
|
||||
this.on('update', fn);
|
||||
// Call to set initial state (before fetching starts)
|
||||
this.emit('update');
|
||||
this._fetchSummary();
|
||||
this._fetchRooms();
|
||||
this._fetchMembers();
|
||||
|
@ -96,6 +113,10 @@ export default class GroupStore extends EventEmitter {
|
|||
this.removeListener('update', fn);
|
||||
}
|
||||
|
||||
isStateReady(id) {
|
||||
return this._ready[id];
|
||||
}
|
||||
|
||||
getSummary() {
|
||||
return this._summary;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue