diff --git a/skins/base/views/molecules/MemberInfo.js b/skins/base/views/molecules/MemberInfo.js index 3a81185ba9..8e1e383f69 100644 --- a/skins/base/views/molecules/MemberInfo.js +++ b/skins/base/views/molecules/MemberInfo.js @@ -67,7 +67,35 @@ module.exports = React.createClass({ if (this.state.active >= 0) { activeAgo = this.getDuration(this.state.active); } + var kickButton, banButton, muteButton, giveModButton; + if (this.state.can.kick) { + kickButton =
+ Kick +
; + } + if (this.state.can.ban) { + banButton =
+ Ban +
; + } + if (this.state.can.mute) { + var muteLabel = this.state.muted ? "Unmute" : "Mute"; + muteButton =
+ {muteLabel} +
; + } + if (this.state.can.modifyLevel) { + var giveOpLabel = this.state.isTargetMod ? "Revoke Mod" : "Make Mod"; + giveModButton =
+ {giveOpLabel} +
+ } + var opLabel; + if (this.state.isTargetMod) { + var level = this.props.member.powerLevelNorm + "%"; + opLabel =
Moderator ({level})
+ } return (
@@ -78,9 +106,14 @@ module.exports = React.createClass({ width="128" height="128" alt=""/>
{this.props.member.userId}
+ {opLabel}
Presence: {this.state.presence}
Last active: {activeAgo}
Start chat
+ {muteButton} + {kickButton} + {banButton} + {giveModButton} ); } diff --git a/src/controllers/molecules/MemberInfo.js b/src/controllers/molecules/MemberInfo.js index 96f8bb8ca8..4f222c5dbf 100644 --- a/src/controllers/molecules/MemberInfo.js +++ b/src/controllers/molecules/MemberInfo.js @@ -18,15 +18,27 @@ limitations under the License. * State vars: * 'presence' : string (online|offline|unavailable etc) * 'active' : number (ms ago; can be -1) + * 'can': { + * kick: boolean, + * ban: boolean, + * mute: boolean, + * modifyLevel: boolean + * }, + * 'muted': boolean, + * 'isTargetMod': boolean */ 'use strict'; var MatrixClientPeg = require("../../MatrixClientPeg"); var dis = require("../../dispatcher"); +var Modal = require("../../Modal"); +var ComponentBroker = require('../../ComponentBroker'); +var ErrorDialog = ComponentBroker.get("organisms/ErrorDialog"); module.exports = { componentDidMount: function() { var self = this; + // listen for presence changes function updateUserState(event, user) { if (!self.props.member) { return; } @@ -40,20 +52,145 @@ module.exports = { MatrixClientPeg.get().on("User.presence", updateUserState); this.userPresenceFn = updateUserState; - if (this.props.member) { - var usr = MatrixClientPeg.get().getUser(this.props.member.userId); - if (!usr) { + // listen for power level changes + function updatePowerLevel(event, member) { + if (!self.props.member) { return; } + + if (member.roomId !== self.props.member.roomId) { return; } - this.setState({ - presence: usr.presence, - active: usr.lastActiveAgo - }); + // only interested in changes to us or them + var myUserId = MatrixClientPeg.get().credentials.userId; + if ([myUserId, self.props.member.userId].indexOf(member.userId) === -1) { + return; + } + self.setState(self._calculateOpsPermissions()); + } + MatrixClientPeg.get().on("RoomMember.powerLevel", updatePowerLevel); + this.updatePowerLevelFn = updatePowerLevel; + + // work out the current state + if (this.props.member) { + var usr = MatrixClientPeg.get().getUser(this.props.member.userId) || {}; + var memberState = this._calculateOpsPermissions(); + memberState.presence = usr.presence || "offline"; + memberState.active = usr.lastActiveAgo || -1; + this.setState(memberState); } }, componentWillUnmount: function() { MatrixClientPeg.get().removeListener("User.presence", this.userPresenceFn); + MatrixClientPeg.get().removeListener( + "RoomMember.powerLevel", this.updatePowerLevelFn + ); + }, + + onKick: function() { + var roomId = this.props.member.roomId; + var target = this.props.member.userId; + var self = this; + MatrixClientPeg.get().kick(roomId, target).done(function() { + // NO-OP; rely on the m.room.member event coming down else we could + // get out of sync if we force setState here! + console.log("Kick success"); + }, function(err) { + Modal.createDialog(ErrorDialog, { + title: "Kick error", + description: err.message + }); + }); + }, + + onBan: function() { + var roomId = this.props.member.roomId; + var target = this.props.member.userId; + var self = this; + MatrixClientPeg.get().ban(roomId, target).done(function() { + // NO-OP; rely on the m.room.member event coming down else we could + // get out of sync if we force setState here! + console.log("Ban success"); + }, function(err) { + Modal.createDialog(ErrorDialog, { + title: "Ban error", + description: err.message + }); + }); + }, + + onMuteToggle: function() { + var roomId = this.props.member.roomId; + var target = this.props.member.userId; + var self = this; + var room = MatrixClientPeg.get().getRoom(roomId); + if (!room) { + return; + } + var powerLevelEvent = room.currentState.getStateEvents( + "m.room.power_levels", "" + ); + if (!powerLevelEvent) { + return; + } + var isMuted = this.state.muted; + var powerLevels = powerLevelEvent.getContent(); + var levelToSend = ( + (powerLevels.events ? powerLevels.events["m.room.message"] : null) || + powerLevels.events_default + ); + var level; + if (isMuted) { // unmute + level = levelToSend; + } + else { // mute + level = levelToSend - 1; + } + + MatrixClientPeg.get().setPowerLevel(roomId, target, level, powerLevelEvent).done( + function() { + // NO-OP; rely on the m.room.member event coming down else we could + // get out of sync if we force setState here! + console.log("Mute toggle success"); + }, function(err) { + Modal.createDialog(ErrorDialog, { + title: "Mute error", + description: err.message + }); + }); + }, + + onModToggle: function() { + var roomId = this.props.member.roomId; + var target = this.props.member.userId; + var room = MatrixClientPeg.get().getRoom(roomId); + if (!room) { + return; + } + var powerLevelEvent = room.currentState.getStateEvents( + "m.room.power_levels", "" + ); + if (!powerLevelEvent) { + return; + } + var me = room.getMember(MatrixClientPeg.get().credentials.userId); + if (!me) { + return; + } + var defaultLevel = powerLevelEvent.getContent().users_default; + var modLevel = me.powerLevel - 1; + // toggle the level + var newLevel = this.state.isTargetMod ? defaultLevel : modLevel; + MatrixClientPeg.get().setPowerLevel(roomId, target, newLevel, powerLevelEvent).done( + function() { + // NO-OP; rely on the m.room.member event coming down else we could + // get out of sync if we force setState here! + console.log("Mod toggle success"); + }, function(err) { + Modal.createDialog(ErrorDialog, { + title: "Mod error", + description: err.message + }); + }); }, onChatClick: function() { @@ -108,8 +245,77 @@ module.exports = { getInitialState: function() { return { presence: "offline", - active: -1 + active: -1, + can: { + kick: false, + ban: false, + mute: false, + modifyLevel: false + }, + muted: false, + isTargetMod: false } + }, + + _calculateOpsPermissions: function() { + var defaultPerms = { + can: {}, + muted: false, + modifyLevel: false + }; + var room = MatrixClientPeg.get().getRoom(this.props.member.roomId); + if (!room) { + return defaultPerms; + } + var powerLevels = room.currentState.getStateEvents( + "m.room.power_levels", "" + ); + if (!powerLevels) { + return defaultPerms; + } + var me = room.getMember(MatrixClientPeg.get().credentials.userId); + var them = this.props.member; + return { + can: this._calculateCanPermissions( + me, them, powerLevels.getContent() + ), + muted: this._isMuted(them, powerLevels.getContent()), + isTargetMod: them.powerLevel > powerLevels.getContent().users_default + }; + }, + + _calculateCanPermissions: function(me, them, powerLevels) { + var can = { + kick: false, + ban: false, + mute: false, + modifyLevel: false + }; + var canAffectUser = them.powerLevel < me.powerLevel; + if (!canAffectUser) { + console.log("Cannot affect user: %s >= %s", them.powerLevel, me.powerLevel); + return can; + } + var editPowerLevel = ( + (powerLevels.events ? powerLevels.events["m.room.power_levels"] : null) || + powerLevels.state_default + ); + can.kick = me.powerLevel >= powerLevels.kick; + can.ban = me.powerLevel >= powerLevels.ban; + can.mute = me.powerLevel >= editPowerLevel; + can.modifyLevel = me.powerLevel > them.powerLevel; + return can; + }, + + _isMuted: function(member, powerLevelContent) { + if (!powerLevelContent || !member) { + return false; + } + var levelToSend = ( + (powerLevelContent.events ? powerLevelContent.events["m.room.message"] : null) || + powerLevelContent.events_default + ); + return member.powerLevel < levelToSend; } };