Fix merge conflict

This commit is contained in:
Stefan Parviainen 2017-09-27 08:41:37 +02:00
commit 35cb52febc
17 changed files with 713 additions and 165 deletions

View file

@ -123,7 +123,6 @@ src/Roles.js
src/Rooms.js src/Rooms.js
src/ScalarAuthClient.js src/ScalarAuthClient.js
src/ScalarMessaging.js src/ScalarMessaging.js
src/TextForEvent.js
src/Tinter.js src/Tinter.js
src/UiEffects.js src/UiEffects.js
src/Unread.js src/Unread.js

View file

@ -107,6 +107,9 @@ export default class BasePlatform {
isElectron(): boolean { return false; } isElectron(): boolean { return false; }
setupScreenSharingForIframe() {
}
/** /**
* Restarts the application, without neccessarily reloading * Restarts the application, without neccessarily reloading
* any application code * any application code

View file

@ -18,11 +18,12 @@ import Modal from './Modal';
import sdk from './'; import sdk from './';
import MultiInviter from './utils/MultiInviter'; import MultiInviter from './utils/MultiInviter';
import { _t } from './languageHandler'; import { _t } from './languageHandler';
import MatrixClientPeg from './MatrixClientPeg';
export function showGroupInviteDialog(groupId) { export function showGroupInviteDialog(groupId) {
const AddressPickerDialog = sdk.getComponent("dialogs.AddressPickerDialog"); const AddressPickerDialog = sdk.getComponent("dialogs.AddressPickerDialog");
Modal.createTrackedDialog('Group Invite', '', AddressPickerDialog, { Modal.createTrackedDialog('Group Invite', '', AddressPickerDialog, {
title: _t('Invite new group members'), title: _t("Invite new group members"),
description: _t("Who would you like to add to this group?"), description: _t("Who would you like to add to this group?"),
placeholder: _t("Name or matrix ID"), placeholder: _t("Name or matrix ID"),
button: _t("Invite to Group"), button: _t("Invite to Group"),
@ -35,6 +36,25 @@ export function showGroupInviteDialog(groupId) {
}); });
} }
export function showGroupAddRoomDialog(groupId) {
return new Promise((resolve, reject) => {
const AddressPickerDialog = sdk.getComponent("dialogs.AddressPickerDialog");
Modal.createTrackedDialog('Add Rooms to Group', '', AddressPickerDialog, {
title: _t("Add rooms to the group"),
description: _t("Which rooms would you like to add to this group?"),
placeholder: _t("Room name or alias"),
button: _t("Add to group"),
pickerType: 'room',
validAddressTypes: ['mx'],
onFinished: (success, addrs) => {
if (!success) return;
_onGroupAddRoomFinished(groupId, addrs).then(resolve, reject);
},
});
});
}
function _onGroupInviteFinished(groupId, addrs) { function _onGroupInviteFinished(groupId, addrs) {
const multiInviter = new MultiInviter(groupId); const multiInviter = new MultiInviter(groupId);
@ -65,3 +85,27 @@ function _onGroupInviteFinished(groupId, addrs) {
}); });
} }
function _onGroupAddRoomFinished(groupId, addrs) {
const errorList = [];
return Promise.all(addrs.map((addr) => {
return MatrixClientPeg.get()
.addRoomToGroup(groupId, addr.address)
.catch(() => { errorList.push(addr.address); })
.reflect();
})).then(() => {
if (errorList.length === 0) {
return;
}
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createTrackedDialog(
'Failed to add the following room to the group',
'', ErrorDialog,
{
title: _t(
"Failed to add the following rooms to %(groupId)s:",
{groupId},
),
description: errorList.join(", "),
});
});
}

View file

@ -13,56 +13,67 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import MatrixClientPeg from "./MatrixClientPeg"; import MatrixClientPeg from './MatrixClientPeg';
import CallHandler from "./CallHandler"; import CallHandler from './CallHandler';
import { _t } from './languageHandler'; import { _t } from './languageHandler';
import * as Roles from './Roles'; import * as Roles from './Roles';
function textForMemberEvent(ev) { function textForMemberEvent(ev) {
// XXX: SYJS-16 "sender is sometimes null for join messages" // XXX: SYJS-16 "sender is sometimes null for join messages"
var senderName = ev.sender ? ev.sender.name : ev.getSender(); const senderName = ev.sender ? ev.sender.name : ev.getSender();
var targetName = ev.target ? ev.target.name : ev.getStateKey(); const targetName = ev.target ? ev.target.name : ev.getStateKey();
var ConferenceHandler = CallHandler.getConferenceHandler(); const prevContent = ev.getPrevContent();
var reason = ev.getContent().reason ? ( const content = ev.getContent();
_t('Reason') + ': ' + ev.getContent().reason
) : ""; const ConferenceHandler = CallHandler.getConferenceHandler();
switch (ev.getContent().membership) { const reason = content.reason ? (_t('Reason') + ': ' + content.reason) : '';
case 'invite': switch (content.membership) {
var threePidContent = ev.getContent().third_party_invite; case 'invite': {
const threePidContent = content.third_party_invite;
if (threePidContent) { if (threePidContent) {
if (threePidContent.display_name) { if (threePidContent.display_name) {
return _t('%(targetName)s accepted the invitation for %(displayName)s.', {targetName: targetName, displayName: threePidContent.display_name}); return _t('%(targetName)s accepted the invitation for %(displayName)s.', {
targetName,
displayName: threePidContent.display_name,
});
} else { } else {
return _t('%(targetName)s accepted an invitation.', {targetName: targetName}); return _t('%(targetName)s accepted an invitation.', {targetName});
} }
} } else {
else {
if (ConferenceHandler && ConferenceHandler.isConferenceUser(ev.getStateKey())) { if (ConferenceHandler && ConferenceHandler.isConferenceUser(ev.getStateKey())) {
return _t('%(senderName)s requested a VoIP conference.', {senderName: senderName}); return _t('%(senderName)s requested a VoIP conference.', {senderName});
} } else {
else { return _t('%(senderName)s invited %(targetName)s.', {senderName, targetName});
return _t('%(senderName)s invited %(targetName)s.', {senderName: senderName, targetName: targetName});
} }
} }
}
case 'ban': case 'ban':
return _t( return _t('%(senderName)s banned %(targetName)s.', {senderName, targetName}) + ' ' + reason;
'%(senderName)s banned %(targetName)s.',
{senderName: senderName, targetName: targetName}
) + ' ' + reason;
case 'join': case 'join':
if (ev.getPrevContent() && ev.getPrevContent().membership == 'join') { if (prevContent && prevContent.membership === 'join') {
if (ev.getPrevContent().displayname && ev.getContent().displayname && ev.getPrevContent().displayname != ev.getContent().displayname) { if (prevContent.displayname && content.displayname && prevContent.displayname !== content.displayname) {
return _t('%(senderName)s changed their display name from %(oldDisplayName)s to %(displayName)s.', {senderName: ev.getSender(), oldDisplayName: ev.getPrevContent().displayname, displayName: ev.getContent().displayname}); return _t('%(senderName)s changed their display name from %(oldDisplayName)s to %(displayName)s.', {
} else if (!ev.getPrevContent().displayname && ev.getContent().displayname) { senderName,
return _t('%(senderName)s set their display name to %(displayName)s.', {senderName: ev.getSender(), displayName: ev.getContent().displayname}); oldDisplayName: prevContent.displayname,
} else if (ev.getPrevContent().displayname && !ev.getContent().displayname) { displayName: content.displayname,
return _t('%(senderName)s removed their display name (%(oldDisplayName)s).', {senderName: ev.getSender(), oldDisplayName: ev.getPrevContent().displayname}); });
} else if (ev.getPrevContent().avatar_url && !ev.getContent().avatar_url) { } else if (!prevContent.displayname && content.displayname) {
return _t('%(senderName)s removed their profile picture.', {senderName: senderName}); return _t('%(senderName)s set their display name to %(displayName)s.', {
} else if (ev.getPrevContent().avatar_url && ev.getContent().avatar_url && ev.getPrevContent().avatar_url != ev.getContent().avatar_url) { senderName,
return _t('%(senderName)s changed their profile picture.', {senderName: senderName}); displayName: content.displayname,
} else if (!ev.getPrevContent().avatar_url && ev.getContent().avatar_url) { });
return _t('%(senderName)s set a profile picture.', {senderName: senderName}); } else if (prevContent.displayname && !content.displayname) {
return _t('%(senderName)s removed their display name (%(oldDisplayName)s).', {
senderName,
oldDisplayName: prevContent.displayname,
});
} else if (prevContent.avatar_url && !content.avatar_url) {
return _t('%(senderName)s removed their profile picture.', {senderName});
} else if (prevContent.avatar_url && content.avatar_url &&
prevContent.avatar_url !== content.avatar_url) {
return _t('%(senderName)s changed their profile picture.', {senderName});
} else if (!prevContent.avatar_url && content.avatar_url) {
return _t('%(senderName)s set a profile picture.', {senderName});
} else { } else {
// suppress null rejoins // suppress null rejoins
return ''; return '';
@ -71,73 +82,69 @@ function textForMemberEvent(ev) {
if (!ev.target) console.warn("Join message has no target! -- " + ev.getContent().state_key); if (!ev.target) console.warn("Join message has no target! -- " + ev.getContent().state_key);
if (ConferenceHandler && ConferenceHandler.isConferenceUser(ev.getStateKey())) { if (ConferenceHandler && ConferenceHandler.isConferenceUser(ev.getStateKey())) {
return _t('VoIP conference started.'); return _t('VoIP conference started.');
} } else {
else { return _t('%(targetName)s joined the room.', {targetName});
return _t('%(targetName)s joined the room.', {targetName: targetName});
} }
} }
case 'leave': case 'leave':
if (ev.getSender() === ev.getStateKey()) { if (ev.getSender() === ev.getStateKey()) {
if (ConferenceHandler && ConferenceHandler.isConferenceUser(ev.getStateKey())) { if (ConferenceHandler && ConferenceHandler.isConferenceUser(ev.getStateKey())) {
return _t('VoIP conference finished.'); return _t('VoIP conference finished.');
} else if (prevContent.membership === "invite") {
return _t('%(targetName)s rejected the invitation.', {targetName});
} else {
return _t('%(targetName)s left the room.', {targetName});
} }
else if (ev.getPrevContent().membership === "invite") { } else if (prevContent.membership === "ban") {
return _t('%(targetName)s rejected the invitation.', {targetName: targetName}); return _t('%(senderName)s unbanned %(targetName)s.', {senderName, targetName});
} } else if (prevContent.membership === "join") {
else { return _t('%(senderName)s kicked %(targetName)s.', {senderName, targetName}) + ' ' + reason;
return _t('%(targetName)s left the room.', {targetName: targetName}); } else if (prevContent.membership === "invite") {
} return _t('%(senderName)s withdrew %(targetName)s\'s invitation.', {
} senderName,
else if (ev.getPrevContent().membership === "ban") { targetName,
return _t('%(senderName)s unbanned %(targetName)s.', {senderName: senderName, targetName: targetName}); }) + ' ' + reason;
} } else {
else if (ev.getPrevContent().membership === "join") { return _t('%(targetName)s left the room.', {targetName});
return _t(
'%(senderName)s kicked %(targetName)s.',
{senderName: senderName, targetName: targetName}
) + ' ' + reason;
}
else if (ev.getPrevContent().membership === "invite") {
return _t(
'%(senderName)s withdrew %(targetName)s\'s invitation.',
{senderName: senderName, targetName: targetName}
) + ' ' + reason;
}
else {
return _t('%(targetName)s left the room.', {targetName: targetName});
} }
} }
} }
function textForTopicEvent(ev) { function textForTopicEvent(ev) {
var senderDisplayName = ev.sender && ev.sender.name ? ev.sender.name : ev.getSender(); const senderDisplayName = ev.sender && ev.sender.name ? ev.sender.name : ev.getSender();
return _t('%(senderDisplayName)s changed the topic to "%(topic)s".', {senderDisplayName: senderDisplayName, topic: ev.getContent().topic}); return _t('%(senderDisplayName)s changed the topic to "%(topic)s".', {
senderDisplayName,
topic: ev.getContent().topic,
});
} }
function textForRoomNameEvent(ev) { function textForRoomNameEvent(ev) {
var senderDisplayName = ev.sender && ev.sender.name ? ev.sender.name : ev.getSender(); const senderDisplayName = ev.sender && ev.sender.name ? ev.sender.name : ev.getSender();
if (!ev.getContent().name || ev.getContent().name.trim().length === 0) { if (!ev.getContent().name || ev.getContent().name.trim().length === 0) {
return _t('%(senderDisplayName)s removed the room name.', {senderDisplayName: senderDisplayName}); return _t('%(senderDisplayName)s removed the room name.', {senderDisplayName});
} }
return _t('%(senderDisplayName)s changed the room name to %(roomName)s.', {senderDisplayName: senderDisplayName, roomName: ev.getContent().name}); return _t('%(senderDisplayName)s changed the room name to %(roomName)s.', {
senderDisplayName,
roomName: ev.getContent().name,
});
} }
function textForMessageEvent(ev) { function textForMessageEvent(ev) {
var senderDisplayName = ev.sender && ev.sender.name ? ev.sender.name : ev.getSender(); const senderDisplayName = ev.sender && ev.sender.name ? ev.sender.name : ev.getSender();
var message = senderDisplayName + ': ' + ev.getContent().body; let message = senderDisplayName + ': ' + ev.getContent().body;
if (ev.getContent().msgtype === "m.emote") { if (ev.getContent().msgtype === "m.emote") {
message = "* " + senderDisplayName + " " + message; message = "* " + senderDisplayName + " " + message;
} else if (ev.getContent().msgtype === "m.image") { } else if (ev.getContent().msgtype === "m.image") {
message = _t('%(senderDisplayName)s sent an image.', {senderDisplayName: senderDisplayName}); message = _t('%(senderDisplayName)s sent an image.', {senderDisplayName});
} }
return message; return message;
} }
function textForCallAnswerEvent(event) { function textForCallAnswerEvent(event) {
var senderName = event.sender ? event.sender.name : _t('Someone'); const senderName = event.sender ? event.sender.name : _t('Someone');
var supported = MatrixClientPeg.get().supportsVoip() ? "" : _t('(not supported by this browser)'); const supported = MatrixClientPeg.get().supportsVoip() ? '' : _t('(not supported by this browser)');
return _t('%(senderName)s answered the call.', {senderName: senderName}) + ' ' + supported; return _t('%(senderName)s answered the call.', {senderName}) + ' ' + supported;
} }
function textForCallHangupEvent(event) { function textForCallHangupEvent(event) {
@ -159,20 +166,23 @@ function textForCallHangupEvent(event) {
} }
function textForCallInviteEvent(event) { function textForCallInviteEvent(event) {
var senderName = event.sender ? event.sender.name : _t('Someone'); const senderName = event.sender ? event.sender.name : _t('Someone');
// FIXME: Find a better way to determine this from the event? // FIXME: Find a better way to determine this from the event?
var type = "voice"; let callType = "voice";
if (event.getContent().offer && event.getContent().offer.sdp && if (event.getContent().offer && event.getContent().offer.sdp &&
event.getContent().offer.sdp.indexOf('m=video') !== -1) { event.getContent().offer.sdp.indexOf('m=video') !== -1) {
type = "video"; callType = "video";
} }
var supported = MatrixClientPeg.get().supportsVoip() ? "" : _t('(not supported by this browser)'); const supported = MatrixClientPeg.get().supportsVoip() ? "" : _t('(not supported by this browser)');
return _t('%(senderName)s placed a %(callType)s call.', {senderName: senderName, callType: type}) + ' ' + supported; return _t('%(senderName)s placed a %(callType)s call.', {senderName, callType}) + ' ' + supported;
} }
function textForThreePidInviteEvent(event) { function textForThreePidInviteEvent(event) {
var senderName = event.sender ? event.sender.name : event.getSender(); const senderName = event.sender ? event.sender.name : event.getSender();
return _t('%(senderName)s sent an invitation to %(targetDisplayName)s to join the room.', {senderName: senderName, targetDisplayName: event.getContent().display_name}); return _t('%(senderName)s sent an invitation to %(targetDisplayName)s to join the room.', {
senderName,
targetDisplayName: event.getContent().display_name,
});
} }
function textForHistoryVisibilityEvent(event) { function textForHistoryVisibilityEvent(event) {
@ -197,8 +207,11 @@ function textForHistoryVisibilityEvent(event) {
} }
function textForEncryptionEvent(event) { function textForEncryptionEvent(event) {
var senderName = event.sender ? event.sender.name : event.getSender(); const senderName = event.sender ? event.sender.name : event.getSender();
return _t('%(senderName)s turned on end-to-end encryption (algorithm %(algorithm)s).', {senderName: senderName, algorithm: event.getContent().algorithm}); return _t('%(senderName)s turned on end-to-end encryption (algorithm %(algorithm)s).', {
senderName,
algorithm: event.getContent().algorithm,
});
} }
// Currently will only display a change if a user's power level is changed // Currently will only display a change if a user's power level is changed
@ -209,18 +222,18 @@ function textForPowerEvent(event) {
} }
const userDefault = event.getContent().users_default || 0; const userDefault = event.getContent().users_default || 0;
// Construct set of userIds // Construct set of userIds
let users = []; const users = [];
Object.keys(event.getContent().users).forEach( Object.keys(event.getContent().users).forEach(
(userId) => { (userId) => {
if (users.indexOf(userId) === -1) users.push(userId); if (users.indexOf(userId) === -1) users.push(userId);
} },
); );
Object.keys(event.getPrevContent().users).forEach( Object.keys(event.getPrevContent().users).forEach(
(userId) => { (userId) => {
if (users.indexOf(userId) === -1) users.push(userId); if (users.indexOf(userId) === -1) users.push(userId);
} },
); );
let diff = []; const diff = [];
// XXX: This is also surely broken for i18n // XXX: This is also surely broken for i18n
users.forEach((userId) => { users.forEach((userId) => {
// Previous power level // Previous power level
@ -229,11 +242,11 @@ function textForPowerEvent(event) {
const to = event.getContent().users[userId]; const to = event.getContent().users[userId];
if (to !== from) { if (to !== from) {
diff.push( diff.push(
_t('%(userId)s from %(fromPowerLevel)s to %(toPowerLevel)s', { _t('%(userId)s from %(fromPowerLevel)s to %(toPowerLevel)s', {
userId: userId, userId: userId,
fromPowerLevel: Roles.textualPowerLevel(from, userDefault), fromPowerLevel: Roles.textualPowerLevel(from, userDefault),
toPowerLevel: Roles.textualPowerLevel(to, userDefault) toPowerLevel: Roles.textualPowerLevel(to, userDefault),
}) }),
); );
} }
}); });
@ -276,14 +289,14 @@ function textForWidgetEvent(event) {
} }
} }
var handlers = { const handlers = {
'm.room.message': textForMessageEvent, 'm.room.message': textForMessageEvent,
'm.room.name': textForRoomNameEvent, 'm.room.name': textForRoomNameEvent,
'm.room.topic': textForTopicEvent, 'm.room.topic': textForTopicEvent,
'm.room.member': textForMemberEvent, 'm.room.member': textForMemberEvent,
'm.call.invite': textForCallInviteEvent, 'm.call.invite': textForCallInviteEvent,
'm.call.answer': textForCallAnswerEvent, 'm.call.answer': textForCallAnswerEvent,
'm.call.hangup': textForCallHangupEvent, 'm.call.hangup': textForCallHangupEvent,
'm.room.third_party_invite': textForThreePidInviteEvent, 'm.room.third_party_invite': textForThreePidInviteEvent,
'm.room.history_visibility': textForHistoryVisibilityEvent, 'm.room.history_visibility': textForHistoryVisibilityEvent,
'm.room.encryption': textForEncryptionEvent, 'm.room.encryption': textForEncryptionEvent,
@ -294,8 +307,8 @@ var handlers = {
module.exports = { module.exports = {
textForEvent: function(ev) { textForEvent: function(ev) {
var hdlr = handlers[ev.getType()]; const hdlr = handlers[ev.getType()];
if (!hdlr) return ""; if (!hdlr) return '';
return hdlr(ev); return hdlr(ev);
} },
}; };

View file

@ -27,6 +27,8 @@ import AccessibleButton from '../views/elements/AccessibleButton';
import Modal from '../../Modal'; import Modal from '../../Modal';
import classnames from 'classnames'; import classnames from 'classnames';
import GroupSummaryStore from '../../stores/GroupSummaryStore';
const RoomSummaryType = PropTypes.shape({ const RoomSummaryType = PropTypes.shape({
room_id: PropTypes.string.isRequired, room_id: PropTypes.string.isRequired,
profile: PropTypes.shape({ profile: PropTypes.shape({
@ -76,8 +78,8 @@ const CategoryRoomList = React.createClass({
if (!success) return; if (!success) return;
const errorList = []; const errorList = [];
Promise.all(addrs.map((addr) => { Promise.all(addrs.map((addr) => {
return MatrixClientPeg.get() return this.context.groupSummaryStore
.addRoomToGroupSummary(this.props.groupId, addr.address) .addRoomToGroupSummary(addr.address)
.catch(() => { errorList.push(addr.address); }) .catch(() => { errorList.push(addr.address); })
.reflect(); .reflect();
})).then(() => { })).then(() => {
@ -153,8 +155,7 @@ const FeaturedRoom = React.createClass({
onDeleteClicked: function(e) { onDeleteClicked: function(e) {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
MatrixClientPeg.get().removeRoomFromGroupSummary( this.context.groupSummaryStore.removeRoomFromGroupSummary(
this.props.groupId,
this.props.summaryInfo.room_id, this.props.summaryInfo.room_id,
).catch((err) => { ).catch((err) => {
console.error('Error whilst removing room from group summary', err); console.error('Error whilst removing room from group summary', err);
@ -242,8 +243,8 @@ const RoleUserList = React.createClass({
if (!success) return; if (!success) return;
const errorList = []; const errorList = [];
Promise.all(addrs.map((addr) => { Promise.all(addrs.map((addr) => {
return MatrixClientPeg.get() return this.context.groupSummaryStore
.addUserToGroupSummary(this.props.groupId, addr.address) .addUserToGroupSummary(addr.address)
.catch(() => { errorList.push(addr.address); }) .catch(() => { errorList.push(addr.address); })
.reflect(); .reflect();
})).then(() => { })).then(() => {
@ -317,8 +318,7 @@ const FeaturedUser = React.createClass({
onDeleteClicked: function(e) { onDeleteClicked: function(e) {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
MatrixClientPeg.get().removeUserFromGroupSummary( this.context.groupSummaryStore.removeUserFromGroupSummary(
this.props.groupId,
this.props.summaryInfo.user_id, this.props.summaryInfo.user_id,
).catch((err) => { ).catch((err) => {
console.error('Error whilst removing user from group summary', err); console.error('Error whilst removing user from group summary', err);
@ -364,6 +364,15 @@ const FeaturedUser = React.createClass({
}, },
}); });
const GroupSummaryContext = {
groupSummaryStore: React.PropTypes.instanceOf(GroupSummaryStore).isRequired,
};
CategoryRoomList.contextTypes = GroupSummaryContext;
FeaturedRoom.contextTypes = GroupSummaryContext;
RoleUserList.contextTypes = GroupSummaryContext;
FeaturedUser.contextTypes = GroupSummaryContext;
export default React.createClass({ export default React.createClass({
displayName: 'GroupView', displayName: 'GroupView',
@ -371,6 +380,16 @@ export default React.createClass({
groupId: PropTypes.string.isRequired, groupId: PropTypes.string.isRequired,
}, },
childContextTypes: {
groupSummaryStore: React.PropTypes.instanceOf(GroupSummaryStore),
},
getChildContext: function() {
return {
groupSummaryStore: this._groupSummaryStore,
};
},
getInitialState: function() { getInitialState: function() {
return { return {
summary: null, summary: null,
@ -379,18 +398,20 @@ export default React.createClass({
saving: false, saving: false,
uploadingAvatar: false, uploadingAvatar: false,
membershipBusy: false, membershipBusy: false,
publicityBusy: false,
}; };
}, },
componentWillMount: function() { componentWillMount: function() {
this._changeAvatarComponent = null; this._changeAvatarComponent = null;
this._loadGroupFromServer(this.props.groupId); this._initGroupSummaryStore(this.props.groupId);
MatrixClientPeg.get().on("Group.myMembership", this._onGroupMyMembership); MatrixClientPeg.get().on("Group.myMembership", this._onGroupMyMembership);
}, },
componentWillUnmount: function() { componentWillUnmount: function() {
MatrixClientPeg.get().removeListener("Group.myMembership", this._onGroupMyMembership); MatrixClientPeg.get().removeListener("Group.myMembership", this._onGroupMyMembership);
this._groupSummaryStore.removeAllListeners();
}, },
componentWillReceiveProps: function(newProps) { componentWillReceiveProps: function(newProps) {
@ -399,7 +420,7 @@ export default React.createClass({
summary: null, summary: null,
error: null, error: null,
}, () => { }, () => {
this._loadGroupFromServer(newProps.groupId); this._initGroupSummaryStore(newProps.groupId);
}); });
} }
}, },
@ -410,13 +431,17 @@ export default React.createClass({
this.setState({membershipBusy: false}); this.setState({membershipBusy: false});
}, },
_loadGroupFromServer: function(groupId) { _initGroupSummaryStore: function(groupId) {
MatrixClientPeg.get().getGroupSummary(groupId).done((res) => { this._groupSummaryStore = new GroupSummaryStore(
MatrixClientPeg.get(), this.props.groupId,
);
this._groupSummaryStore.on('update', () => {
this.setState({ this.setState({
summary: res, summary: this._groupSummaryStore.getSummary(),
error: null, error: null,
}); });
}, (err) => { });
this._groupSummaryStore.on('error', (err) => {
this.setState({ this.setState({
summary: null, summary: null,
error: err, error: err,
@ -493,7 +518,7 @@ export default React.createClass({
editing: false, editing: false,
summary: null, summary: null,
}); });
this._loadGroupFromServer(this.props.groupId); this._initGroupSummaryStore(this.props.groupId);
}).catch((e) => { }).catch((e) => {
this.setState({ this.setState({
saving: false, saving: false,
@ -560,7 +585,26 @@ export default React.createClass({
}); });
}, },
_getFeaturedRoomsNode() { _onPubliciseOffClick: function() {
this._setPublicity(false);
},
_onPubliciseOnClick: function() {
this._setPublicity(true);
},
_setPublicity: function(publicity) {
this.setState({
publicityBusy: true,
});
this._groupSummaryStore.setGroupPublicity(publicity).then(() => {
this.setState({
publicityBusy: false,
});
});
},
_getFeaturedRoomsNode: function() {
const summary = this.state.summary; const summary = this.state.summary;
const defaultCategoryRooms = []; const defaultCategoryRooms = [];
@ -601,7 +645,7 @@ export default React.createClass({
</div>; </div>;
}, },
_getFeaturedUsersNode() { _getFeaturedUsersNode: function() {
const summary = this.state.summary; const summary = this.state.summary;
const noRoleUsers = []; const noRoleUsers = [];
@ -643,12 +687,12 @@ export default React.createClass({
}, },
_getMembershipSection: function() { _getMembershipSection: function() {
const Spinner = sdk.getComponent("elements.Spinner");
const group = MatrixClientPeg.get().getGroup(this.props.groupId); const group = MatrixClientPeg.get().getGroup(this.props.groupId);
if (!group) return null; if (!group) return null;
if (group.myMembership === 'invite') { if (group.myMembership === 'invite') {
const Spinner = sdk.getComponent("elements.Spinner");
if (this.state.membershipBusy) { if (this.state.membershipBusy) {
return <div className="mx_GroupView_membershipSection"> return <div className="mx_GroupView_membershipSection">
<Spinner /> <Spinner />
@ -677,17 +721,57 @@ export default React.createClass({
if (this.state.summary.user && this.state.summary.user.is_privileged) { if (this.state.summary.user && this.state.summary.user.is_privileged) {
youAreAMemberText = _t("You are an administrator of this group"); youAreAMemberText = _t("You are an administrator of this group");
} }
return <div className="mx_GroupView_membershipSection mx_GroupView_membershipSection_joined">
<div className="mx_GroupView_membershipSection_description"> let publicisedButton;
{youAreAMemberText} if (this.state.publicityBusy) {
</div> publicisedButton = <Spinner />;
<div className="mx_GroupView_membership_buttonContainer"> }
<AccessibleButton className="mx_GroupView_textButton mx_RoomHeader_textButton"
onClick={this._onLeaveClick} let publicisedSection;
if (this.state.summary.user && this.state.summary.user.is_publicised) {
if (!this.state.publicityBusy) {
publicisedButton = <AccessibleButton className="mx_GroupView_textButton mx_RoomHeader_textButton"
onClick={this._onPubliciseOffClick}
>
{_t("Unpublish")}
</AccessibleButton>;
}
publicisedSection = <div className="mx_GroupView_membershipSubSection">
{_t("This group is published on your profile")}
<div className="mx_GroupView_membership_buttonContainer">
{publicisedButton}
</div>
</div>;
} else {
if (!this.state.publicityBusy) {
publicisedButton = <AccessibleButton className="mx_GroupView_textButton mx_RoomHeader_textButton"
onClick={this._onPubliciseOnClick}
> >
{_t("Leave")} {_t("Publish")}
</AccessibleButton> </AccessibleButton>;
}
publicisedSection = <div className="mx_GroupView_membershipSubSection">
{_t("This group is not published on your profile")}
<div className="mx_GroupView_membership_buttonContainer">
{publicisedButton}
</div>
</div>;
}
return <div className="mx_GroupView_membershipSection mx_GroupView_membershipSection_joined">
<div className="mx_GroupView_membershipSubSection">
<div className="mx_GroupView_membershipSection_description">
{youAreAMemberText}
</div>
<div className="mx_GroupView_membership_buttonContainer">
<AccessibleButton className="mx_GroupView_textButton mx_RoomHeader_textButton"
onClick={this._onLeaveClick}
>
{_t("Leave")}
</AccessibleButton>
</div>
</div> </div>
{publicisedSection}
</div>; </div>;
} }

View file

@ -155,7 +155,7 @@ module.exports = React.createClass({
if (this.props.groupId) { if (this.props.groupId) {
this._doNaiveGroupRoomSearch(query); this._doNaiveGroupRoomSearch(query);
} else { } else {
console.error('Room searching only implemented for groups'); this._doRoomSearch(query);
} }
} else { } else {
console.error('Unknown pickerType', this.props.pickerType); console.error('Unknown pickerType', this.props.pickerType);
@ -248,7 +248,7 @@ module.exports = React.createClass({
results.push({ results.push({
room_id: r.room_id, room_id: r.room_id,
avatar_url: r.avatar_url, avatar_url: r.avatar_url,
name: r.name, name: r.name || r.canonical_alias,
}); });
}); });
this._processResults(results, query); this._processResults(results, query);
@ -264,6 +264,37 @@ module.exports = React.createClass({
}); });
}, },
_doRoomSearch: function(query) {
const lowerCaseQuery = query.toLowerCase();
const rooms = MatrixClientPeg.get().getRooms();
const results = [];
rooms.forEach((room) => {
const nameEvent = room.currentState.getStateEvents('m.room.name', '');
const topicEvent = room.currentState.getStateEvents('m.room.topic', '');
const name = nameEvent ? nameEvent.getContent().name : '';
const canonicalAlias = room.getCanonicalAlias();
const topic = topicEvent ? topicEvent.getContent().topic : '';
const nameMatch = (name || '').toLowerCase().includes(lowerCaseQuery);
const aliasMatch = (canonicalAlias || '').toLowerCase().includes(lowerCaseQuery);
const topicMatch = (topic || '').toLowerCase().includes(lowerCaseQuery);
if (!(nameMatch || topicMatch || aliasMatch)) {
return;
}
const avatarEvent = room.currentState.getStateEvents('m.room.avatar', '');
const avatarUrl = avatarEvent ? avatarEvent.getContent().url : undefined;
results.push({
room_id: room.roomId,
avatar_url: avatarUrl,
name: name || canonicalAlias,
});
});
this._processResults(results, query);
this.setState({
busy: false,
});
},
_doUserDirectorySearch: function(query) { _doUserDirectorySearch: function(query) {
this.setState({ this.setState({
busy: true, busy: true,

View file

@ -19,6 +19,7 @@ limitations under the License.
import url from 'url'; import url from 'url';
import React from 'react'; import React from 'react';
import MatrixClientPeg from '../../../MatrixClientPeg'; import MatrixClientPeg from '../../../MatrixClientPeg';
import PlatformPeg from '../../../PlatformPeg';
import ScalarAuthClient from '../../../ScalarAuthClient'; import ScalarAuthClient from '../../../ScalarAuthClient';
import SdkConfig from '../../../SdkConfig'; import SdkConfig from '../../../SdkConfig';
import Modal from '../../../Modal'; import Modal from '../../../Modal';
@ -127,6 +128,30 @@ export default React.createClass({
loading: false, loading: false,
}); });
}); });
window.addEventListener('message', this._onMessage, false);
},
componentWillUnmount() {
window.removeEventListener('message', this._onMessage);
},
_onMessage(event) {
if (this.props.type !== 'jitsi') {
return;
}
if (!event.origin) {
event.origin = event.originalEvent.origin;
}
if (!this.state.widgetUrl.startsWith(event.origin)) {
return;
}
if (event.data.widgetAction === 'jitsi_iframe_loaded') {
const iframe = this.refs.appFrame.contentWindow
.document.querySelector('iframe[id^="jitsiConferenceFrame"]');
PlatformPeg.get().setupScreenSharingForIframe(iframe);
}
}, },
_canUserModify: function() { _canUserModify: function() {

View file

@ -29,6 +29,9 @@ const BULK_REQUEST_DEBOUNCE_MS = 200;
// If true, flair can function and we should keep sending requests for groups and avatars. // If true, flair can function and we should keep sending requests for groups and avatars.
let groupSupport = true; let groupSupport = true;
const USER_GROUPS_CACHE_BUST_MS = 1800000; // 30 mins
const GROUP_PROFILES_CACHE_BUST_MS = 1800000; // 30 mins
// TODO: Cache-busting based on time. (The server won't inform us of membership changes.) // TODO: Cache-busting based on time. (The server won't inform us of membership changes.)
// This applies to userGroups and groupProfiles. We can provide a slightly better UX by // This applies to userGroups and groupProfiles. We can provide a slightly better UX by
// cache-busting when the current user joins/leaves a group. // cache-busting when the current user joins/leaves a group.
@ -69,7 +72,9 @@ function getPublicisedGroupsCached(matrixClient, userId) {
usersPending[userId].reject = reject; usersPending[userId].reject = reject;
}).then((groups) => { }).then((groups) => {
userGroups[userId] = groups; userGroups[userId] = groups;
// TODO: Reset cache at this point setTimeout(() => {
delete userGroups[userId];
}, USER_GROUPS_CACHE_BUST_MS);
return userGroups[userId]; return userGroups[userId];
}).catch((err) => { }).catch((err) => {
throw err; throw err;
@ -126,6 +131,9 @@ async function getGroupProfileCached(matrixClient, groupId) {
groupId, groupId,
avatarUrl: profile.avatar_url, avatarUrl: profile.avatar_url,
}; };
setTimeout(() => {
delete groupProfiles[groupId];
}, GROUP_PROFILES_CACHE_BUST_MS);
return groupProfiles[groupId]; return groupProfiles[groupId];
} }

View file

@ -152,12 +152,21 @@ module.exports = withMatrixClient(React.createClass({
</div>; </div>;
} }
const BaseAvatar = sdk.getComponent('avatars.BaseAvatar'); const avatarUrl = this.props.matrixClient.mxcUrlToHttp(
const avatar = ( this.props.groupMember.avatarUrl,
<BaseAvatar name={this.props.groupMember.userId} width={36} height={36} /> 36, 36, 'crop',
); );
const groupMemberName = this.props.groupMember.userId; const BaseAvatar = sdk.getComponent('avatars.BaseAvatar');
const avatar = (
<BaseAvatar name={this.props.groupMember.userId} width={36} height={36}
url={avatarUrl}
/>
);
const groupMemberName = (
this.props.groupMember.displayname || this.props.groupMember.userId
);
const EmojiText = sdk.getComponent('elements.EmojiText'); const EmojiText = sdk.getComponent('elements.EmojiText');
return ( return (

View file

@ -131,7 +131,7 @@ export default withMatrixClient(React.createClass({
const inputBox = ( const inputBox = (
<form autoComplete="off"> <form autoComplete="off">
<input className="mx_MemberList_query" id="mx_MemberList_query" type="text" <input className="mx_GroupMemberList_query" id="mx_GroupMemberList_query" type="text"
onChange={this.onSearchQueryChanged} value={this.state.searchQuery} onChange={this.onSearchQueryChanged} value={this.state.searchQuery}
placeholder={ _t('Filter group members') } /> placeholder={ _t('Filter group members') } />
</form> </form>

View file

@ -48,11 +48,15 @@ export default withMatrixClient(React.createClass({
const EntityTile = sdk.getComponent('rooms.EntityTile'); const EntityTile = sdk.getComponent('rooms.EntityTile');
const name = this.props.member.displayname || this.props.member.userId; const name = this.props.member.displayname || this.props.member.userId;
const avatarUrl = this.props.matrixClient.mxcUrlToHttp(
this.props.member.avatarUrl,
36, 36, 'crop',
);
const av = ( const av = (
<BaseAvatar name={this.props.member.userId} <BaseAvatar name={this.props.member.userId}
width={36} height={36} width={36} height={36}
url={this.props.matrixClient.mxcUrlToHttp(this.props.member.avatarUrl)} url={avatarUrl}
/> />
); );

View file

@ -0,0 +1,143 @@
/*
Copyright 2017 New Vector Ltd.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import { _t } from '../../../languageHandler';
import sdk from '../../../index';
import { groupRoomFromApiObject } from '../../../groups';
import GeminiScrollbar from 'react-gemini-scrollbar';
import PropTypes from 'prop-types';
import {MatrixClient} from 'matrix-js-sdk';
const INITIAL_LOAD_NUM_ROOMS = 30;
export default React.createClass({
contextTypes: {
matrixClient: React.PropTypes.instanceOf(MatrixClient).isRequired,
},
propTypes: {
groupId: PropTypes.string.isRequired,
},
getInitialState: function() {
return {
fetching: false,
rooms: null,
truncateAt: INITIAL_LOAD_NUM_ROOMS,
searchQuery: "",
};
},
componentWillMount: function() {
this._unmounted = false;
this._fetchRooms();
},
_fetchRooms: function() {
this.setState({fetching: true});
this.context.matrixClient.getGroupRooms(this.props.groupId).then((result) => {
this.setState({
rooms: result.chunk.map((apiRoom) => {
return groupRoomFromApiObject(apiRoom);
}),
fetching: false,
});
}).catch((e) => {
this.setState({fetching: false});
console.error("Failed to get group room list: ", e);
});
},
_createOverflowTile: function(overflowCount, totalCount) {
// For now we'll pretend this is any entity. It should probably be a separate tile.
const EntityTile = sdk.getComponent("rooms.EntityTile");
const BaseAvatar = sdk.getComponent("avatars.BaseAvatar");
const text = _t("and %(count)s others...", { count: overflowCount });
return (
<EntityTile className="mx_EntityTile_ellipsis" avatarJsx={
<BaseAvatar url="img/ellipsis.svg" name="..." width={36} height={36} />
} name={text} presenceState="online" suppressOnHover={true}
onClick={this._showFullRoomList} />
);
},
_showFullRoomList: function() {
this.setState({
truncateAt: -1,
});
},
onSearchQueryChanged: function(ev) {
this.setState({ searchQuery: ev.target.value });
},
makeGroupRoomTiles: function(query) {
const GroupRoomTile = sdk.getComponent("groups.GroupRoomTile");
query = (query || "").toLowerCase();
let roomList = this.state.rooms;
if (query) {
roomList = roomList.filter((room) => {
const matchesName = (room.name || "").toLowerCase().include(query);
const matchesAlias = (room.canonicalAlias || "").toLowerCase().includes(query);
return matchesName || matchesAlias;
});
}
roomList = roomList.map((groupRoom, index) => {
return (
<GroupRoomTile
key={index}
groupId={this.props.groupId}
groupRoom={groupRoom} />
);
});
return roomList;
},
render: function() {
if (this.state.fetching) {
const Spinner = sdk.getComponent("elements.Spinner");
return (<div className="mx_GroupRoomList">
<Spinner />
</div>);
} else if (this.state.rooms === null) {
return null;
}
const inputBox = (
<form autoComplete="off">
<input className="mx_GroupRoomList_query" id="mx_GroupRoomList_query" type="text"
onChange={this.onSearchQueryChanged} value={this.state.searchQuery}
placeholder={ _t('Filter group rooms') } />
</form>
);
const TruncatedList = sdk.getComponent("elements.TruncatedList");
return (
<div className="mx_GroupRoomList">
{ inputBox }
<GeminiScrollbar autoshow={true} className="mx_GroupRoomList_joined mx_GroupRoomList_outerWrapper">
<TruncatedList className="mx_GroupRoomList_wrapper" truncateAt={this.state.truncateAt}
createOverflowElement={this._createOverflowTile}>
{this.makeGroupRoomTiles(this.state.searchQuery)}
</TruncatedList>
</GeminiScrollbar>
</div>
);
},
});

View file

@ -0,0 +1,89 @@
/*
Copyright 2017 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import {MatrixClient} from 'matrix-js-sdk';
import { _t } from '../../../languageHandler';
import PropTypes from 'prop-types';
import sdk from '../../../index';
import dis from '../../../dispatcher';
import { GroupRoomType } from '../../../groups';
const GroupRoomTile = React.createClass({
displayName: 'GroupRoomTile',
propTypes: {
groupId: PropTypes.string.isRequired,
groupRoom: GroupRoomType.isRequired,
},
getInitialState: function() {
return {};
},
onClick: function(e) {
let roomId;
let roomAlias;
if (this.props.groupRoom.canonicalAlias) {
roomAlias = this.props.groupRoom.canonicalAlias;
} else {
roomId = this.props.groupRoom.roomId;
}
dis.dispatch({
action: 'view_room',
room_id: roomId,
room_alias: roomAlias,
});
},
render: function() {
const BaseAvatar = sdk.getComponent('avatars.BaseAvatar');
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
const name = this.props.groupRoom.name ||
this.props.groupRoom.canonicalAlias ||
_t("Unnamed Room");
const avatarUrl = this.context.matrixClient.mxcUrlToHttp(
this.props.groupRoom.avatarUrl,
36, 36, 'crop',
);
const av = (
<BaseAvatar name={name}
width={36} height={36}
url={avatarUrl}
/>
);
return (
<AccessibleButton className="mx_GroupRoomTile" onClick={this.onClick}>
<div className="mx_GroupRoomTile_avatar">
{av}
</div>
<div className="mx_GroupRoomTile_name">
{name}
</div>
</AccessibleButton>
);
},
});
GroupRoomTile.contextTypes = {
matrixClient: React.PropTypes.instanceOf(MatrixClient).isRequired,
};
export default GroupRoomTile;

View file

@ -18,12 +18,7 @@ limitations under the License.
import React from 'react'; import React from 'react';
import { _t } from '../../../languageHandler'; import { _t } from '../../../languageHandler';
import classNames from 'classnames';
import Matrix from 'matrix-js-sdk';
import Promise from 'bluebird';
var MatrixClientPeg = require("../../../MatrixClientPeg"); var MatrixClientPeg = require("../../../MatrixClientPeg");
var Modal = require("../../../Modal");
var Entities = require("../../../Entities");
var sdk = require('../../../index'); var sdk = require('../../../index');
var GeminiScrollbar = require('react-gemini-scrollbar'); var GeminiScrollbar = require('react-gemini-scrollbar');
var rate_limited_func = require('../../../ratelimitedfunc'); var rate_limited_func = require('../../../ratelimitedfunc');
@ -31,30 +26,26 @@ var CallHandler = require("../../../CallHandler");
const INITIAL_LOAD_NUM_MEMBERS = 30; const INITIAL_LOAD_NUM_MEMBERS = 30;
const INITIAL_LOAD_NUM_INVITED = 5; const INITIAL_LOAD_NUM_INVITED = 5;
const SHOW_MORE_INCREMENT = 100;
module.exports = React.createClass({ module.exports = React.createClass({
displayName: 'MemberList', displayName: 'MemberList',
getInitialState: function() { getInitialState: function() {
const state = { this.memberDict = this.getMemberDict();
members: [], const members = this.roomMembers();
return {
members: members,
filteredJoinedMembers: this._filterMembers(members, 'join'),
filteredInvitedMembers: this._filterMembers(members, 'invite'),
// ideally we'd size this to the page height, but // ideally we'd size this to the page height, but
// in practice I find that a little constraining // in practice I find that a little constraining
truncateAtJoined: INITIAL_LOAD_NUM_MEMBERS, truncateAtJoined: INITIAL_LOAD_NUM_MEMBERS,
truncateAtInvited: INITIAL_LOAD_NUM_INVITED, truncateAtInvited: INITIAL_LOAD_NUM_INVITED,
searchQuery: "", searchQuery: "",
}; };
if (!this.props.roomId) return state;
var cli = MatrixClientPeg.get();
var room = cli.getRoom(this.props.roomId);
if (!room) return state;
this.memberDict = this.getMemberDict();
state.members = this.roomMembers();
state.filteredJoinedMembers = this._filterMembers(state.members, 'join');
state.filteredInvitedMembers = this._filterMembers(state.members, 'invite');
return state;
}, },
componentWillMount: function() { componentWillMount: function() {
@ -207,11 +198,11 @@ module.exports = React.createClass({
}, },
_createOverflowTileJoined: function(overflowCount, totalCount) { _createOverflowTileJoined: function(overflowCount, totalCount) {
return this._createOverflowTile(overflowCount, totalCount, this._showFullJoinedMemberList); return this._createOverflowTile(overflowCount, totalCount, this._showMoreJoinedMemberList);
}, },
_createOverflowTileInvited: function(overflowCount, totalCount) { _createOverflowTileInvited: function(overflowCount, totalCount) {
return this._createOverflowTile(overflowCount, totalCount, this._showFullInvitedMemberList); return this._createOverflowTile(overflowCount, totalCount, this._showMoreInvitedMemberList);
}, },
_createOverflowTile: function(overflowCount, totalCount, onClick) { _createOverflowTile: function(overflowCount, totalCount, onClick) {
@ -227,15 +218,15 @@ module.exports = React.createClass({
); );
}, },
_showFullJoinedMemberList: function() { _showMoreJoinedMemberList: function() {
this.setState({ this.setState({
truncateAtJoined: -1 truncateAtJoined: this.state.truncateAtJoined + SHOW_MORE_INCREMENT,
}); });
}, },
_showFullInvitedMemberList: function() { _showMoreInvitedMemberList: function() {
this.setState({ this.setState({
truncateAtInvited: -1 truncateAtInvited: this.state.truncateAtInvited + SHOW_MORE_INCREMENT,
}); });
}, },

View file

@ -22,6 +22,14 @@ export const GroupMemberType = PropTypes.shape({
avatarUrl: PropTypes.string, avatarUrl: PropTypes.string,
}); });
export const GroupRoomType = PropTypes.shape({
name: PropTypes.string,
// TODO: API doesn't return this yet
// roomId: PropTypes.string.isRequired,
canonicalAlias: PropTypes.string,
avatarUrl: PropTypes.string,
});
export function groupMemberFromApiObject(apiObject) { export function groupMemberFromApiObject(apiObject) {
return { return {
userId: apiObject.user_id, userId: apiObject.user_id,
@ -29,3 +37,11 @@ export function groupMemberFromApiObject(apiObject) {
avatarUrl: apiObject.avatar_url, avatarUrl: apiObject.avatar_url,
}; };
} }
export function groupRoomFromApiObject(apiObject) {
return {
name: apiObject.name,
canonicalAlias: apiObject.canonical_alias,
avatarUrl: apiObject.avatar_url,
};
}

View file

@ -846,6 +846,7 @@
"Robot check is currently unavailable on desktop - please use a <a>web browser</a>": "Robot check is currently unavailable on desktop - please use a <a>web browser</a>", "Robot check is currently unavailable on desktop - please use a <a>web browser</a>": "Robot check is currently unavailable on desktop - please use a <a>web browser</a>",
"Description": "Description", "Description": "Description",
"Filter group members": "Filter group members", "Filter group members": "Filter group members",
"Filter group rooms": "Filter group rooms",
"Remove from group": "Remove from group", "Remove from group": "Remove from group",
"Invite new group members": "Invite new group members", "Invite new group members": "Invite new group members",
"Who would you like to add to this group?": "Who would you like to add to this group?", "Who would you like to add to this group?": "Who would you like to add to this group?",
@ -876,11 +877,22 @@
"Which rooms would you like to add to this summary?": "Which rooms would you like to add to this summary?", "Which rooms would you like to add to this summary?": "Which rooms would you like to add to this summary?",
"Room name or alias": "Room name or alias", "Room name or alias": "Room name or alias",
"You are an administrator of this group": "You are an administrator of this group", "You are an administrator of this group": "You are an administrator of this group",
"Failed to add the following rooms to the summary of %(groupId)s:": "Failed to add the following rooms to the summary of %(groupId)s:",
"Failed to remove the room from the summary of %(groupId)s": "Failed to remove the room from the summary of %(groupId)s", "Failed to remove the room from the summary of %(groupId)s": "Failed to remove the room from the summary of %(groupId)s",
"The room '%(roomName)' could not be removed from the summary.": "The room '%(roomName)' could not be removed from the summary.", "The room '%(roomName)s' could not be removed from the summary.": "The room '%(roomName)s' could not be removed from the summary.",
"Failed to remove a user from the summary of %(groupId)s": "Failed to remove a user from the summary of %(groupId)s", "Failed to remove a user from the summary of %(groupId)s": "Failed to remove a user from the summary of %(groupId)s",
"The user '%(displayName)s' could not be removed from the summary.": "The user '%(displayName)s' could not be removed from the summary.", "The user '%(displayName)s' could not be removed from the summary.": "The user '%(displayName)s' could not be removed from the summary.",
"Light theme": "Light theme", "Light theme": "Light theme",
"Dark theme": "Dark theme", "Dark theme": "Dark theme",
"Unknown": "Unknown" "Unknown": "Unknown",
"Failed to add the following rooms to the summary of %(groupId)s:": "Failed to add the following rooms to the summary of %(groupId)s:",
"The room '%(roomName)s' could not be removed from the summary.": "The room '%(roomName)s' could not be removed from the summary.",
"Add rooms to the group": "Add rooms to the group",
"Which rooms would you like to add to this group?": "Which rooms would you like to add to this group?",
"Add to group": "Add to group",
"Failed to add the following rooms to %(groupId)s:": "Failed to add the following rooms to %(groupId)s:",
"Unpublish": "Unpublish",
"This group is published on your profile": "This group is published on your profile",
"Publish": "Publish",
"This group is not published on your profile": "This group is not published on your profile"
} }

View file

@ -0,0 +1,77 @@
/*
Copyright 2017 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import EventEmitter from 'events';
/**
* Stores the group summary for a room and provides an API to change it
*/
export default class GroupSummaryStore extends EventEmitter {
constructor(matrixClient, groupId) {
super();
this._groupId = groupId;
this._matrixClient = matrixClient;
this._summary = {};
this._fetchSummary();
}
_fetchSummary() {
this._matrixClient.getGroupSummary(this._groupId).then((resp) => {
this._summary = resp;
this._notifyListeners();
}).catch((err) => {
this.emit('error', err);
});
}
_notifyListeners() {
this.emit('update');
}
getSummary() {
return this._summary;
}
addRoomToGroupSummary(roomId, categoryId) {
return this._matrixClient
.addRoomToGroupSummary(this._groupId, roomId, categoryId)
.then(this._fetchSummary.bind(this));
}
addUserToGroupSummary(userId, roleId) {
return this._matrixClient
.addUserToGroupSummary(this._groupId, userId, roleId)
.then(this._fetchSummary.bind(this));
}
removeRoomFromGroupSummary(roomId) {
return this._matrixClient
.removeRoomFromGroupSummary(this._groupId, roomId)
.then(this._fetchSummary.bind(this));
}
removeUserFromGroupSummary(userId) {
return this._matrixClient
.removeUserFromGroupSummary(this._groupId, userId)
.then(this._fetchSummary.bind(this));
}
setGroupPublicity(isPublished) {
return this._matrixClient
.setGroupPublicity(this._groupId, isPublished)
.then(this._fetchSummary.bind(this));
}
}