- );
+ if (!this.props.editable || (this.state.phase == this.Phases.Display && (this.props.label || this.props.labelClassName) && !this.value)) {
+ // show the label
+ editable_el =
{ this.props.label || this.props.initialValue }
;
+ } else {
+ // show the content editable div, but manually manage its contents as react and contentEditable don't play nice together
+ editable_el = ;
}
- return (
-
- {editable_el}
-
- );
+ return editable_el;
}
});
diff --git a/src/components/views/elements/PowerSelector.js b/src/components/views/elements/PowerSelector.js
new file mode 100644
index 0000000000..c47c9f3809
--- /dev/null
+++ b/src/components/views/elements/PowerSelector.js
@@ -0,0 +1,108 @@
+/*
+Copyright 2015, 2016 OpenMarket 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.
+*/
+
+'use strict';
+
+var React = require('react');
+
+var roles = {
+ 0: 'User',
+ 50: 'Moderator',
+ 100: 'Admin',
+};
+
+var reverseRoles = {};
+Object.keys(roles).forEach(function(key) {
+ reverseRoles[roles[key]] = key;
+});
+
+module.exports = React.createClass({
+ displayName: 'PowerSelector',
+
+ propTypes: {
+ value: React.PropTypes.number.isRequired,
+ disabled: React.PropTypes.bool,
+ onChange: React.PropTypes.func,
+ },
+
+ getInitialState: function() {
+ return {
+ custom: (roles[this.props.value] === undefined),
+ };
+ },
+
+ onSelectChange: function(event) {
+ this.state.custom = (event.target.value === "Custom");
+ this.props.onChange(this.getValue());
+ },
+
+ onCustomBlur: function(event) {
+ this.props.onChange(this.getValue());
+ },
+
+ onCustomKeyDown: function(event) {
+ if (event.key == "Enter") {
+ this.props.onChange(this.getValue());
+ }
+ },
+
+ getValue: function() {
+ var value;
+ if (this.refs.select) {
+ value = reverseRoles[ this.refs.select.value ];
+ if (this.refs.custom) {
+ if (value === undefined) value = parseInt( this.refs.custom.value );
+ }
+ }
+ return value;
+ },
+
+ render: function() {
+ var customPicker;
+ if (this.state.custom) {
+ var input;
+ if (this.props.disabled) {
+ input = { this.props.value }
+ }
+ else {
+ input =
+ }
+ customPicker = of { input };
+ }
+
+ var selectValue = roles[this.props.value] || "Custom";
+ var select;
+ if (this.props.disabled) {
+ select = { selectValue };
+ }
+ else {
+ select =
+
+ }
+
+ return (
+
+ { select }
+ { customPicker }
+
+ );
+ }
+});
diff --git a/src/components/views/login/CustomServerDialog.js b/src/components/views/login/CustomServerDialog.js
index 8a67dfd7e6..dc6a49abd6 100644
--- a/src/components/views/login/CustomServerDialog.js
+++ b/src/components/views/login/CustomServerDialog.js
@@ -22,7 +22,7 @@ module.exports = React.createClass({
render: function() {
return (
-
+
Custom Server Options
diff --git a/src/components/views/messages/TextualBody.js b/src/components/views/messages/TextualBody.js
index fe763d06bf..e3613ef9a3 100644
--- a/src/components/views/messages/TextualBody.js
+++ b/src/components/views/messages/TextualBody.js
@@ -36,6 +36,9 @@ module.exports = React.createClass({
},
componentDidUpdate: function() {
+ // XXX: why don't we linkify here?
+ // XXX: why do we bother doing this on update at all, given events are immutable?
+
if (this.props.mxEvent.getContent().format === "org.matrix.custom.html")
HtmlUtils.highlightDom(ReactDOM.findDOMNode(this));
},
diff --git a/src/components/views/rooms/MemberInfo.js b/src/components/views/rooms/MemberInfo.js
index b5f0b88b40..a8a601c2d6 100644
--- a/src/components/views/rooms/MemberInfo.js
+++ b/src/components/views/rooms/MemberInfo.js
@@ -58,15 +58,16 @@ module.exports = React.createClass({
var roomId = this.props.member.roomId;
var target = this.props.member.userId;
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
- });
- });
+ // 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
+ });
+ }
+ );
this.props.onFinished();
},
@@ -74,16 +75,18 @@ module.exports = React.createClass({
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
var roomId = this.props.member.roomId;
var target = this.props.member.userId;
- 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
- });
- });
+ 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
+ });
+ }
+ );
this.props.onFinished();
},
@@ -118,16 +121,17 @@ module.exports = React.createClass({
}
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
- });
- });
+ 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
+ });
+ }
+ );
this.props.onFinished();
},
@@ -154,22 +158,55 @@ module.exports = React.createClass({
}
var defaultLevel = powerLevelEvent.getContent().users_default;
var modLevel = me.powerLevel - 1;
+ if (modLevel > 50 && defaultLevel < 50) modLevel = 50; // try to stick with the vector level defaults
// 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
- });
- });
+ 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
+ });
+ }
+ );
this.props.onFinished();
},
+ onPowerChange: function(powerLevel) {
+ var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
+ var roomId = this.props.member.roomId;
+ var target = this.props.member.userId;
+ var room = MatrixClientPeg.get().getRoom(roomId);
+ if (!room) {
+ this.props.onFinished();
+ return;
+ }
+ var powerLevelEvent = room.currentState.getStateEvents(
+ "m.room.power_levels", ""
+ );
+ if (!powerLevelEvent) {
+ this.props.onFinished();
+ return;
+ }
+ MatrixClientPeg.get().setPowerLevel(roomId, target, powerLevel, 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("Power change success");
+ }, function(err) {
+ Modal.createDialog(ErrorDialog, {
+ title: "Failure to change power level",
+ description: err.message
+ });
+ }
+ );
+ this.props.onFinished();
+ },
+
onChatClick: function() {
// check if there are any existing rooms with just us and them (1:1)
// If so, just view that room. If not, create a private room with them.
@@ -209,20 +246,22 @@ module.exports = React.createClass({
MatrixClientPeg.get().createRoom({
invite: [this.props.member.userId],
preset: "private_chat"
- }).done(function(res) {
- self.setState({ creatingRoom: false });
- dis.dispatch({
- action: 'view_room',
- room_id: res.room_id
- });
- self.props.onFinished();
- }, function(err) {
- self.setState({ creatingRoom: false });
- console.error(
- "Failed to create room: %s", JSON.stringify(err)
- );
- self.props.onFinished();
- });
+ }).done(
+ function(res) {
+ self.setState({ creatingRoom: false });
+ dis.dispatch({
+ action: 'view_room',
+ room_id: res.room_id
+ });
+ self.props.onFinished();
+ }, function(err) {
+ self.setState({ creatingRoom: false });
+ console.error(
+ "Failed to create room: %s", JSON.stringify(err)
+ );
+ self.props.onFinished();
+ }
+ );
}
},
@@ -291,9 +330,15 @@ module.exports = React.createClass({
(powerLevels.events ? powerLevels.events["m.room.power_levels"] : null) ||
powerLevels.state_default
);
+ var levelToSend = (
+ (powerLevels.events ? powerLevels.events["m.room.message"] : null) ||
+ powerLevels.events_default
+ );
+
can.kick = me.powerLevel >= powerLevels.kick;
can.ban = me.powerLevel >= powerLevels.ban;
can.mute = me.powerLevel >= editPowerLevel;
+ can.toggleMod = me.powerLevel > them.powerLevel && them.powerLevel >= levelToSend;
can.modifyLevel = me.powerLevel > them.powerLevel;
return can;
},
@@ -317,12 +362,11 @@ module.exports = React.createClass({
},
render: function() {
- var interactButton, kickButton, banButton, muteButton, giveModButton, spinner;
- if (this.props.member.userId === MatrixClientPeg.get().credentials.userId) {
- interactButton =
Leave room
;
- }
- else {
- interactButton =
Start chat
;
+ var startChat, kickButton, banButton, muteButton, giveModButton, spinner;
+ if (this.props.member.userId !== MatrixClientPeg.get().credentials.userId) {
+ // FIXME: we're referring to a vector component from react-sdk
+ var BottomLeftMenuTile = sdk.getComponent('rooms.BottomLeftMenuTile');
+ startChat =
}
if (this.state.creatingRoom) {
@@ -346,35 +390,56 @@ module.exports = React.createClass({
{muteLabel}
;
}
- if (this.state.can.modifyLevel) {
- var giveOpLabel = this.state.isTargetMod ? "Revoke Mod" : "Make Mod";
+ if (this.state.can.toggleMod) {
+ var giveOpLabel = this.state.isTargetMod ? "Revoke Moderator" : "Make Moderator";
giveModButton =
{giveOpLabel}
}
+ // TODO: we should have an invite button if this MemberInfo is showing a user who isn't actually in the current room yet
+ // e.g. clicking on a linkified userid in a room
+
+ var adminTools;
+ if (kickButton || banButton || muteButton || giveModButton) {
+ adminTools =
+
}
else {
- var topic = this.props.room.currentState.getStateEvents('m.room.topic', '');
-
var name = null;
var searchStatus = null;
var topic_el = null;
var cancel_button = null;
var save_button = null;
var settings_button = null;
- var actual_name = this.props.room.currentState.getStateEvents('m.room.name', '');
- if (actual_name) actual_name = actual_name.getContent().name;
if (this.props.editing) {
- name =
-
-
-
- // if (topic) topic_el =
- cancel_button =
Cancel
- save_button =
Save Changes
- } else {
- //
+ // calculate permissions. XXX: this should be done on mount or something, and factored out with RoomSettings
+ var power_levels = this.props.room.currentState.getStateEvents('m.room.power_levels', '');
+ var events_levels = (power_levels ? power_levels.events : {}) || {};
+ var user_id = MatrixClientPeg.get().credentials.userId;
+
+ if (power_levels) {
+ power_levels = power_levels.getContent();
+ var default_user_level = parseInt(power_levels.users_default || 0);
+ var user_levels = power_levels.users || {};
+ var current_user_level = user_levels[user_id];
+ if (current_user_level == undefined) current_user_level = default_user_level;
+ } else {
+ var default_user_level = 0;
+ var user_levels = [];
+ var current_user_level = 0;
+ }
+ var state_default = parseInt((power_levels ? power_levels.state_default : 0) || 0);
+
+ var room_avatar_level = state_default;
+ if (events_levels['m.room.avatar'] !== undefined) {
+ room_avatar_level = events_levels['m.room.avatar'];
+ }
+ var can_set_room_avatar = current_user_level >= room_avatar_level;
+
+ var room_name_level = state_default;
+ if (events_levels['m.room.name'] !== undefined) {
+ room_name_level = events_levels['m.room.name'];
+ }
+ var can_set_room_name = current_user_level >= room_name_level;
+
+ var room_topic_level = state_default;
+ if (events_levels['m.room.topic'] !== undefined) {
+ room_topic_level = events_levels['m.room.topic'];
+ }
+ var can_set_room_topic = current_user_level >= room_topic_level;
+
+ var placeholderName = "Unnamed Room";
+ if (this.state.defaultName && this.state.defaultName !== '?') {
+ placeholderName += " (" + this.state.defaultName + ")";
+ }
+
+ save_button =
Save
+ cancel_button =
+ }
+
+ if (can_set_room_name) {
+ name =
+
+
+
+ }
+ else {
var searchStatus;
// don't display the search count until the search completes and
// gives us a valid (possibly zero) searchCount.
@@ -116,21 +219,55 @@ module.exports = React.createClass({
}
name =
-
+
{ this.props.room.name }
{ searchStatus }
- if (topic) topic_el =
{ topic.getContent().topic }
;
+ }
+
+ if (can_set_room_topic) {
+ topic_el =
+
+ } else {
+ var topic = this.props.room.currentState.getStateEvents('m.room.topic', '');
+ if (topic) topic_el =
{ topic.getContent().topic }
;
}
var roomAvatar = null;
if (this.props.room) {
- roomAvatar = (
-
- );
+ if (can_set_room_avatar) {
+ roomAvatar = (
+