Merge branch 'develop' into departify

pull/21833/head
Stefan Parviainen 2017-10-24 18:27:24 +02:00
commit 4ff369c884
8 changed files with 124 additions and 56 deletions

View File

@ -232,9 +232,14 @@ for (const path of SEARCH_PATHS) {
const trObj = {}; const trObj = {};
for (const tr of translatables) { for (const tr of translatables) {
trObj[tr] = tr;
if (tr.includes("|")) { if (tr.includes("|")) {
trObj[tr] = inputTranslationsRaw[tr]; if (inputTranslationsRaw[tr]) {
trObj[tr] = inputTranslationsRaw[tr];
} else {
trObj[tr] = tr.split("|")[0];
}
} else {
trObj[tr] = tr;
} }
} }

View File

@ -481,6 +481,10 @@ export default React.createClass({
editing: true, editing: true,
profileForm: Object.assign({}, this.state.summary.profile), profileForm: Object.assign({}, this.state.summary.profile),
}); });
dis.dispatch({
action: 'ui_opacity',
sideOpacity: 0.3,
});
}, },
_onCancelClick: function() { _onCancelClick: function() {
@ -488,6 +492,7 @@ export default React.createClass({
editing: false, editing: false,
profileForm: null, profileForm: null,
}); });
dis.dispatch({action: 'ui_opacity'});
}, },
_onNameChange: function(value) { _onNameChange: function(value) {
@ -535,12 +540,16 @@ export default React.createClass({
_onSaveClick: function() { _onSaveClick: function() {
this.setState({saving: true}); this.setState({saving: true});
MatrixClientPeg.get().setGroupProfile(this.props.groupId, this.state.profileForm).then((result) => { const savePromise = this.state.isUserPrivileged ?
MatrixClientPeg.get().setGroupProfile(this.props.groupId, this.state.profileForm) :
Promise.resolve();
savePromise.then((result) => {
this.setState({ this.setState({
saving: false, saving: false,
editing: false, editing: false,
summary: null, summary: null,
}); });
dis.dispatch({action: 'ui_opacity'});
this._initGroupStore(this.props.groupId); this._initGroupStore(this.props.groupId);
}).catch((e) => { }).catch((e) => {
this.setState({ this.setState({
@ -624,23 +633,40 @@ export default React.createClass({
}); });
}, },
_getGroupSection: function() {
const groupSettingsSectionClasses = classnames({
"mx_GroupView_group": this.state.editing,
"mx_GroupView_group_disabled": this.state.editing && !this.state.isUserPrivileged,
});
const header = this.state.editing ? <h2> { _t('Community Settings') } </h2> : <div />;
return <div className={groupSettingsSectionClasses}>
{ header }
{ this._getLongDescriptionNode() }
{ this._getRoomsNode() }
</div>;
},
_getRoomsNode: function() { _getRoomsNode: function() {
const RoomDetailList = sdk.getComponent('rooms.RoomDetailList'); const RoomDetailList = sdk.getComponent('rooms.RoomDetailList');
const AccessibleButton = sdk.getComponent('elements.AccessibleButton'); const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
const TintableSvg = sdk.getComponent('elements.TintableSvg'); const TintableSvg = sdk.getComponent('elements.TintableSvg');
const addButton = this.state.editing ?
(<AccessibleButton onClick={this._onAddRoomsClick} > const addRoomRow = this.state.editing ?
<div className="mx_GroupView_rooms_header_addButton" > (<AccessibleButton className="mx_GroupView_rooms_header_addRow"
onClick={this._onAddRoomsClick}
>
<div className="mx_GroupView_rooms_header_addRow_button">
<TintableSvg src="img/icons-room-add.svg" width="24" height="24" /> <TintableSvg src="img/icons-room-add.svg" width="24" height="24" />
</div> </div>
<div className="mx_GroupView_rooms_header_addButton_label"> <div className="mx_GroupView_rooms_header_addRow_label">
{ _t('Add rooms to this community') } { _t('Add rooms to this community') }
</div> </div>
</AccessibleButton>) : <div />; </AccessibleButton>) : <div />;
return <div className="mx_GroupView_rooms"> return <div className="mx_GroupView_rooms">
<div className="mx_GroupView_rooms_header"> <div className="mx_GroupView_rooms_header">
<h3>{ _t('Rooms') }</h3> <h3>{ _t('Rooms') }</h3>
{ addButton } { addRoomRow }
</div> </div>
<RoomDetailList rooms={this._groupStore.getGroupRooms()} /> <RoomDetailList rooms={this._groupStore.getGroupRooms()} />
</div>; </div>;
@ -790,7 +816,7 @@ export default React.createClass({
_getMemberSettingsSection: function() { _getMemberSettingsSection: function() {
return <div className="mx_GroupView_memberSettings"> return <div className="mx_GroupView_memberSettings">
<h3> { _t("Community Member Settings") } </h3> <h2> { _t("Community Member Settings") } </h2>
<div className="mx_GroupView_memberSettings_toggle"> <div className="mx_GroupView_memberSettings_toggle">
<input type="checkbox" <input type="checkbox"
onClick={this._onPublicityToggle} onClick={this._onPublicityToggle}
@ -813,8 +839,13 @@ export default React.createClass({
if (summary.profile && summary.profile.long_description) { if (summary.profile && summary.profile.long_description) {
description = sanitizedHtmlNode(summary.profile.long_description); description = sanitizedHtmlNode(summary.profile.long_description);
} }
return this.state.editing && this.state.isUserPrivileged ? const groupDescEditingClasses = classnames({
<div className="mx_GroupView_groupDesc"> "mx_GroupView_groupDesc": true,
"mx_GroupView_groupDesc_disabled": !this.state.isUserPrivileged,
});
return this.state.editing ?
<div className={groupDescEditingClasses}>
<h3> { _t("Long Description (HTML)") } </h3> <h3> { _t("Long Description (HTML)") } </h3>
<textarea <textarea
value={this.state.profileForm.long_description} value={this.state.profileForm.long_description}
@ -844,14 +875,10 @@ export default React.createClass({
const bodyNodes = [ const bodyNodes = [
this._getMembershipSection(), this._getMembershipSection(),
this.state.editing ? this._getMemberSettingsSection() : null, this.state.editing ? this._getMemberSettingsSection() : null,
this._getLongDescriptionNode(), this._getGroupSection(),
this._getRoomsNode(),
]; ];
const rightButtons = []; const rightButtons = [];
const headerClasses = { if (this.state.editing && this.state.isUserPrivileged) {
mx_GroupView_header: true,
};
if (this.state.editing) {
let avatarImage; let avatarImage;
if (this.state.uploadingAvatar) { if (this.state.uploadingAvatar) {
avatarImage = <Spinner />; avatarImage = <Spinner />;
@ -900,19 +927,6 @@ export default React.createClass({
onValueChanged={this._onShortDescChange} onValueChanged={this._onShortDescChange}
tabIndex="2" tabIndex="2"
dir="auto" />; dir="auto" />;
rightButtons.push(
<AccessibleButton className="mx_GroupView_textButton mx_RoomHeader_textButton"
onClick={this._onSaveClick} key="_saveButton"
>
{ _t('Save') }
</AccessibleButton>,
);
rightButtons.push(
<AccessibleButton className="mx_RoomHeader_cancelButton" onClick={this._onCancelClick} key="_cancelButton">
<img src="img/cancel.svg" className="mx_filterFlipColor"
width="18" height="18" alt={_t("Cancel")} />
</AccessibleButton>,
);
} else { } else {
const groupAvatarUrl = summary.profile ? summary.profile.avatar_url : null; const groupAvatarUrl = summary.profile ? summary.profile.avatar_url : null;
avatarNode = <GroupAvatar avatarNode = <GroupAvatar
@ -934,6 +948,22 @@ export default React.createClass({
if (summary.profile && summary.profile.short_description) { if (summary.profile && summary.profile.short_description) {
shortDescNode = <span onClick={this._onEditClick}>{ summary.profile.short_description }</span>; shortDescNode = <span onClick={this._onEditClick}>{ summary.profile.short_description }</span>;
} }
}
if (this.state.editing) {
rightButtons.push(
<AccessibleButton className="mx_GroupView_textButton mx_RoomHeader_textButton"
onClick={this._onSaveClick} key="_saveButton"
>
{ _t('Save') }
</AccessibleButton>,
);
rightButtons.push(
<AccessibleButton className="mx_RoomHeader_cancelButton" onClick={this._onCancelClick} key="_cancelButton">
<img src="img/cancel.svg" className="mx_filterFlipColor"
width="18" height="18" alt={_t("Cancel")} />
</AccessibleButton>,
);
} else {
rightButtons.push( rightButtons.push(
<AccessibleButton className="mx_GroupHeader_button" <AccessibleButton className="mx_GroupHeader_button"
onClick={this._onEditClick} title={_t("Community Settings")} key="_editButton" onClick={this._onEditClick} title={_t("Community Settings")} key="_editButton"
@ -950,10 +980,13 @@ export default React.createClass({
</AccessibleButton>, </AccessibleButton>,
); );
} }
headerClasses.mx_GroupView_header_view = true;
} }
const headerClasses = {
mx_GroupView_header: true,
mx_GroupView_header_view: !this.state.editing,
};
return ( return (
<div className="mx_GroupView"> <div className="mx_GroupView">
<div className={classnames(headerClasses)}> <div className={classnames(headerClasses)}>

View File

@ -59,6 +59,7 @@ export default withMatrixClient(React.createClass({
}, },
_fetchMembers: function() { _fetchMembers: function() {
if (this._unmounted) return;
this.setState({ this.setState({
members: this._groupStore.getGroupMembers(), members: this._groupStore.getGroupMembers(),
invitedMembers: this._groupStore.getGroupInvitedMembers(), invitedMembers: this._groupStore.getGroupInvitedMembers(),
@ -105,12 +106,11 @@ export default withMatrixClient(React.createClass({
}); });
} }
memberList = memberList.map((m) => { const uniqueMembers = {};
return ( memberList.forEach((m) => {
<GroupMemberTile key={m.userId} groupId={this.props.groupId} member={m} /> if (!uniqueMembers[m.userId]) uniqueMembers[m.userId] = m;
);
}); });
memberList = Object.keys(uniqueMembers).map((userId) => uniqueMembers[userId]);
memberList.sort((a, b) => { memberList.sort((a, b) => {
// TODO: should put admins at the top: we don't yet have that info // TODO: should put admins at the top: we don't yet have that info
if (a < b) { if (a < b) {
@ -122,10 +122,16 @@ export default withMatrixClient(React.createClass({
} }
}); });
const memberTiles = memberList.map((m) => {
return (
<GroupMemberTile key={m.userId} groupId={this.props.groupId} member={m} />
);
});
return <TruncatedList className="mx_MemberList_wrapper" truncateAt={this.state.truncateAt} return <TruncatedList className="mx_MemberList_wrapper" truncateAt={this.state.truncateAt}
createOverflowElement={this._createOverflowTile} createOverflowElement={this._createOverflowTile}
> >
{ memberList } { memberTiles }
</TruncatedList>; </TruncatedList>;
}, },

View File

@ -25,15 +25,28 @@ import MatrixClientPeg from '../../../MatrixClientPeg';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
function getDisplayAliasForRoom(room) { function getDisplayAliasForRoom(room) {
return room.canonical_alias || (room.aliases ? room.aliases[0] : ""); return room.canonicalAlias || (room.aliases ? room.aliases[0] : "");
} }
const RoomDetailRow = React.createClass({ const RoomDetailRow = React.createClass({
propTypes: PropTypes.shape({
name: PropTypes.string,
topic: PropTypes.string,
roomId: PropTypes.string,
avatarUrl: PropTypes.string,
numJoinedMembers: PropTypes.number,
canonicalAlias: PropTypes.string,
aliases: PropTypes.arrayOf(PropTypes.string),
worldReadable: PropTypes.bool,
guestCanJoin: PropTypes.bool,
}),
onClick: function(ev) { onClick: function(ev) {
ev.preventDefault(); ev.preventDefault();
dis.dispatch({ dis.dispatch({
action: 'view_room', action: 'view_room',
room_id: this.props.room.room_id, room_id: this.props.room.roomId,
}); });
}, },
@ -50,10 +63,10 @@ const RoomDetailRow = React.createClass({
const name = room.name || getDisplayAliasForRoom(room) || _t('Unnamed room'); const name = room.name || getDisplayAliasForRoom(room) || _t('Unnamed room');
const topic = linkifyString(sanitizeHtml(room.topic || '')); const topic = linkifyString(sanitizeHtml(room.topic || ''));
const guestRead = room.world_readable ? ( const guestRead = room.worldReadable ? (
<div className="mx_RoomDirectory_perm">{ _t('World readable') }</div> <div className="mx_RoomDirectory_perm">{ _t('World readable') }</div>
) : <div />; ) : <div />;
const guestJoin = room.guest_can_join ? ( const guestJoin = room.guestCanJoin ? (
<div className="mx_RoomDirectory_perm">{ _t('Guests can join') }</div> <div className="mx_RoomDirectory_perm">{ _t('Guests can join') }</div>
) : <div />; ) : <div />;
@ -62,13 +75,13 @@ const RoomDetailRow = React.createClass({
{ guestJoin } { guestJoin }
</div>) : <div />; </div>) : <div />;
return <tr key={room.room_id} onClick={this.onClick}> return <tr key={room.roomId} onClick={this.onClick}>
<td className="mx_RoomDirectory_roomAvatar"> <td className="mx_RoomDirectory_roomAvatar">
<BaseAvatar width={24} height={24} resizeMethod='crop' <BaseAvatar width={24} height={24} resizeMethod='crop'
name={name} idName={name} name={name} idName={name}
url={ContentRepo.getHttpUriForMxc( url={ContentRepo.getHttpUriForMxc(
MatrixClientPeg.get().getHomeserverUrl(), MatrixClientPeg.get().getHomeserverUrl(),
room.avatar_url, 24, 24, "crop")} /> room.avatarUrl, 24, 24, "crop")} />
</td> </td>
<td className="mx_RoomDirectory_roomDescription"> <td className="mx_RoomDirectory_roomDescription">
<div className="mx_RoomDirectory_name">{ name }</div>&nbsp; <div className="mx_RoomDirectory_name">{ name }</div>&nbsp;
@ -79,7 +92,7 @@ const RoomDetailRow = React.createClass({
<div className="mx_RoomDirectory_alias">{ getDisplayAliasForRoom(room) }</div> <div className="mx_RoomDirectory_alias">{ getDisplayAliasForRoom(room) }</div>
</td> </td>
<td className="mx_RoomDirectory_roomMemberCount"> <td className="mx_RoomDirectory_roomMemberCount">
{ room.num_joined_members } { room.numJoinedMembers }
</td> </td>
</tr>; </tr>;
}, },
@ -92,13 +105,14 @@ export default React.createClass({
rooms: PropTypes.arrayOf(PropTypes.shape({ rooms: PropTypes.arrayOf(PropTypes.shape({
name: PropTypes.string, name: PropTypes.string,
topic: PropTypes.string, topic: PropTypes.string,
room_id: PropTypes.string, roomId: PropTypes.string,
num_joined_members: PropTypes.number, avatarUrl: PropTypes.string,
canonical_alias: PropTypes.string, numJoinedMembers: PropTypes.number,
canonicalAlias: PropTypes.string,
aliases: PropTypes.arrayOf(PropTypes.string), aliases: PropTypes.arrayOf(PropTypes.string),
world_readable: PropTypes.bool, worldReadable: PropTypes.bool,
guest_can_join: PropTypes.bool, guestCanJoin: PropTypes.bool,
})), })),
}, },

View File

@ -43,5 +43,9 @@ export function groupRoomFromApiObject(apiObject) {
roomId: apiObject.room_id, roomId: apiObject.room_id,
canonicalAlias: apiObject.canonical_alias, canonicalAlias: apiObject.canonical_alias,
avatarUrl: apiObject.avatar_url, avatarUrl: apiObject.avatar_url,
topic: apiObject.topic,
numJoinedMembers: apiObject.num_joined_members,
worldReadable: apiObject.world_readable,
guestCanJoin: apiObject.guest_can_join,
}; };
} }

View File

@ -54,8 +54,6 @@
"Room name or alias": "Room name or alias", "Room name or alias": "Room name or alias",
"Add to community": "Add to community", "Add to community": "Add to community",
"Failed to invite the following users to %(groupId)s:": "Failed to invite the following users to %(groupId)s:", "Failed to invite the following users to %(groupId)s:": "Failed to invite the following users to %(groupId)s:",
"Invites sent": "Invites sent",
"Your community invitations have been sent.": "Your community invitations have been sent.",
"Failed to invite users to community": "Failed to invite users to community", "Failed to invite users to community": "Failed to invite users to community",
"Failed to invite users to %(groupId)s": "Failed to invite users to %(groupId)s", "Failed to invite users to %(groupId)s": "Failed to invite users to %(groupId)s",
"Failed to add the following rooms to %(groupId)s:": "Failed to add the following rooms to %(groupId)s:", "Failed to add the following rooms to %(groupId)s:": "Failed to add the following rooms to %(groupId)s:",
@ -156,6 +154,7 @@
"Message Pinning": "Message Pinning", "Message Pinning": "Message Pinning",
"%(displayName)s is typing": "%(displayName)s is typing", "%(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 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",
"%(names)s and %(lastPerson)s are typing": "%(names)s and %(lastPerson)s are typing", "%(names)s and %(lastPerson)s are typing": "%(names)s and %(lastPerson)s are typing",
"Failure to create room": "Failure to create room", "Failure to create room": "Failure to create room",
"Server may be unavailable, overloaded, or you hit a bug.": "Server may be unavailable, overloaded, or you hit a bug.", "Server may be unavailable, overloaded, or you hit a bug.": "Server may be unavailable, overloaded, or you hit a bug.",
@ -565,6 +564,7 @@
"Custom level": "Custom level", "Custom level": "Custom level",
"Room directory": "Room directory", "Room directory": "Room directory",
"Start chat": "Start chat", "Start chat": "Start chat",
"And %(count)s more...|other": "And %(count)s more...",
"ex. @bob:example.com": "ex. @bob:example.com", "ex. @bob:example.com": "ex. @bob:example.com",
"Add User": "Add User", "Add User": "Add User",
"Something went wrong!": "Something went wrong!", "Something went wrong!": "Something went wrong!",

View File

@ -33,8 +33,7 @@ class GroupStoreCache {
} }
} }
let singletonGroupStoreCache = null; if (global.singletonGroupStoreCache === undefined) {
if (!singletonGroupStoreCache) { global.singletonGroupStoreCache = new GroupStoreCache();
singletonGroupStoreCache = new GroupStoreCache();
} }
module.exports = singletonGroupStoreCache; export default global.singletonGroupStoreCache;

View File

@ -72,6 +72,13 @@ class RoomViewStore extends Store {
case 'view_room': case 'view_room':
this._viewRoom(payload); this._viewRoom(payload);
break; break;
case 'view_my_groups':
case 'view_group':
this._setState({
roomId: null,
roomAlias: null,
});
break;
case 'view_room_error': case 'view_room_error':
this._viewRoomError(payload); this._viewRoomError(payload);
break; break;