diff --git a/src/Tinter.js b/src/Tinter.js
index 3e7949b65d..3612be5b10 100644
--- a/src/Tinter.js
+++ b/src/Tinter.js
@@ -63,6 +63,7 @@ var cssAttrs = [
"backgroundColor",
"borderColor",
"borderTopColor",
+ "borderBottomColor",
];
var svgAttrs = [
diff --git a/src/components/structures/MatrixChat.js b/src/components/structures/MatrixChat.js
index 462933cbc6..71910b6f31 100644
--- a/src/components/structures/MatrixChat.js
+++ b/src/components/structures/MatrixChat.js
@@ -65,6 +65,7 @@ module.exports = React.createClass({
collapse_rhs: false,
ready: false,
width: 10000,
+ autoPeek: true, // by default, we peek into rooms when we try to join them
};
if (s.logged_in) {
if (MatrixClientPeg.get().getRooms().length) {
@@ -304,6 +305,9 @@ module.exports = React.createClass({
});
break;
case 'view_room':
+ // by default we autoPeek rooms, unless we were called explicitly with
+ // autoPeek=false by something like RoomDirectory who has already peeked
+ this.setState({ autoPeek : payload.auto_peek === false ? false : true });
this._viewRoom(payload.room_id, payload.show_settings);
break;
case 'view_prev_room':
@@ -787,6 +791,7 @@ module.exports = React.createClass({
);
diff --git a/src/components/structures/RoomView.js b/src/components/structures/RoomView.js
index b333a18331..e2a9f8b730 100644
--- a/src/components/structures/RoomView.js
+++ b/src/components/structures/RoomView.js
@@ -57,7 +57,9 @@ if (DEBUG_SCROLL) {
module.exports = React.createClass({
displayName: 'RoomView',
propTypes: {
- ConferenceHandler: React.PropTypes.any
+ ConferenceHandler: React.PropTypes.any,
+ roomId: React.PropTypes.string,
+ autoPeek: React.PropTypes.bool, // should we try to peek the room on mount, or has whoever invoked us already initiated a peek?
},
/* properties in RoomView objects include:
@@ -78,7 +80,9 @@ module.exports = React.createClass({
syncState: MatrixClientPeg.get().getSyncState(),
hasUnsentMessages: this._hasUnsentMessages(room),
callState: null,
+ autoPeekDone: false, // track whether our autoPeek (if any) has completed)
guestsCanJoin: false,
+ canPeek: false,
readMarkerEventId: room ? room.getEventReadUpTo(MatrixClientPeg.get().credentials.userId) : null,
readMarkerGhostEventId: undefined
}
@@ -86,6 +90,7 @@ module.exports = React.createClass({
componentWillMount: function() {
this.dispatcherRef = dis.register(this.onAction);
+ MatrixClientPeg.get().on("Room", this.onNewRoom);
MatrixClientPeg.get().on("Room.timeline", this.onRoomTimeline);
MatrixClientPeg.get().on("Room.name", this.onRoomName);
MatrixClientPeg.get().on("Room.accountData", this.onRoomAccountData);
@@ -111,27 +116,21 @@ module.exports = React.createClass({
// We can /peek though. If it fails then we present the join UI. If it
// succeeds then great, show the preview (but we still may be able to /join!).
if (!this.state.room) {
- console.log("Attempting to peek into room %s", this.props.roomId);
- MatrixClientPeg.get().peekInRoom(this.props.roomId).done(() => {
- // we don't need to do anything - JS SDK will emit Room events
- // which will update the UI. We *do* however need to know if we
- // can join the room so we can fiddle with the UI appropriately.
- var peekedRoom = MatrixClientPeg.get().getRoom(this.props.roomId);
- if (!peekedRoom) {
- return;
- }
- var guestAccessEvent = peekedRoom.currentState.getStateEvents("m.room.guest_access", "");
- if (!guestAccessEvent) {
- return;
- }
- if (guestAccessEvent.getContent().guest_access === "can_join") {
+ if (this.props.autoPeek) {
+ console.log("Attempting to peek into room %s", this.props.roomId);
+ MatrixClientPeg.get().peekInRoom(this.props.roomId).catch((err) => {
+ console.error("Failed to peek into room: %s", err);
+ }).finally(() => {
+ // we don't need to do anything - JS SDK will emit Room events
+ // which will update the UI.
this.setState({
- guestsCanJoin: true
+ autoPeekDone: true
});
- }
- }, function(err) {
- console.error("Failed to peek into room: %s", err);
- });
+ });
+ }
+ }
+ else {
+ this._calculatePeekRules(this.state.room);
}
},
@@ -155,6 +154,7 @@ module.exports = React.createClass({
}
dis.unregister(this.dispatcherRef);
if (MatrixClientPeg.get()) {
+ MatrixClientPeg.get().removeListener("Room", this.onNewRoom);
MatrixClientPeg.get().removeListener("Room.timeline", this.onRoomTimeline);
MatrixClientPeg.get().removeListener("Room.name", this.onRoomName);
MatrixClientPeg.get().removeListener("Room.accountData", this.onRoomAccountData);
@@ -278,6 +278,32 @@ module.exports = React.createClass({
});
},
+ onNewRoom: function(room) {
+ if (room.roomId == this.props.roomId) {
+ this.setState({
+ room: room
+ });
+ }
+
+ this._calculatePeekRules(room);
+ },
+
+ _calculatePeekRules: function(room) {
+ var guestAccessEvent = room.currentState.getStateEvents("m.room.guest_access", "");
+ if (guestAccessEvent && guestAccessEvent.getContent().guest_access === "can_join") {
+ this.setState({
+ guestsCanJoin: true
+ });
+ }
+
+ var historyVisibility = room.currentState.getStateEvents("m.room.history_visibility", "");
+ if (historyVisibility && historyVisibility.getContent().history_visibility === "world_readable") {
+ this.setState({
+ canPeek: true
+ });
+ }
+ },
+
onRoomName: function(room) {
if (room.roomId == this.props.roomId) {
this.setState({
@@ -349,6 +375,14 @@ module.exports = React.createClass({
if (member.roomId === this.props.roomId) {
// a member state changed in this room, refresh the tab complete list
this._updateTabCompleteList(this.state.room);
+
+ var room = MatrixClientPeg.get().getRoom(this.props.roomId);
+ var me = MatrixClientPeg.get().credentials.userId;
+ if (this.state.joining && room.hasMembershipState(me, "join")) {
+ this.setState({
+ joining: false
+ });
+ }
}
if (!this.props.ConferenceHandler) {
@@ -522,10 +556,17 @@ module.exports = React.createClass({
onJoinButtonClicked: function(ev) {
var self = this;
- MatrixClientPeg.get().joinRoom(this.props.roomId).then(function() {
+ MatrixClientPeg.get().joinRoom(this.props.roomId).done(function() {
+ // It is possible that there is no Room yet if state hasn't come down
+ // from /sync - joinRoom will resolve when the HTTP request to join succeeds,
+ // NOT when it comes down /sync. If there is no room, we'll keep the
+ // joining flag set until we see it. Likewise, if our state is not
+ // "join" we'll keep this flag set until it comes down /sync.
+ var room = MatrixClientPeg.get().getRoom(self.props.roomId);
+ var me = MatrixClientPeg.get().credentials.userId;
self.setState({
- joining: false,
- room: MatrixClientPeg.get().getRoom(self.props.roomId)
+ joining: room ? !room.hasMembershipState(me, "join") : true,
+ room: room
});
}, function(error) {
self.setState({
@@ -929,15 +970,36 @@ module.exports = React.createClass({
);
}
+ var visibilityDeferred;
if (old_history_visibility != newVals.history_visibility &&
newVals.history_visibility != undefined) {
- deferreds.push(
+ visibilityDeferred =
MatrixClientPeg.get().sendStateEvent(
this.state.room.roomId, "m.room.history_visibility", {
history_visibility: newVals.history_visibility,
}, ""
- )
- );
+ );
+ }
+
+ if (old_guest_read != newVals.guest_read ||
+ old_guest_join != newVals.guest_join)
+ {
+ var guestDeferred =
+ MatrixClientPeg.get().setGuestAccess(this.state.room.roomId, {
+ allowRead: newVals.guest_read,
+ allowJoin: newVals.guest_join
+ });
+
+ if (visibilityDeferred) {
+ visibilityDeferred = visibilityDeferred.then(guestDeferred);
+ }
+ else {
+ visibilityDeferred = guestDeferred;
+ }
+ }
+
+ if (visibilityDeferred) {
+ deferreds.push(visibilityDeferred);
}
// setRoomMutePushRule will do nothing if there is no change
@@ -1040,17 +1102,6 @@ module.exports = React.createClass({
);
}
- if (old_guest_read != newVals.guest_read ||
- old_guest_join != newVals.guest_join)
- {
- deferreds.push(
- MatrixClientPeg.get().setGuestAccess(this.state.room.roomId, {
- allowRead: newVals.guest_read,
- allowJoin: newVals.guest_join
- })
- );
- }
-
if (deferreds.length) {
var self = this;
q.allSettled(deferreds).then(
@@ -1399,12 +1450,30 @@ module.exports = React.createClass({
if (!this.state.room) {
if (this.props.roomId) {
- return (
-
- Join Room
-
- );
- } else {
+ if (this.props.autoPeek && !this.state.autoPeekDone) {
+ var Loader = sdk.getComponent("elements.Spinner");
+ return (
+
+
+
+ );
+ }
+ else {
+ var joinErrorText = this.state.joinError ? "Failed to join room!" : "";
+ return (
+
+ );
+ }
+ }
+ else {
return (
);
@@ -1425,19 +1494,26 @@ module.exports = React.createClass({
var inviteEvent = myMember.events.member;
var inviterName = inviteEvent.sender ? inviteEvent.sender.name : inviteEvent.getSender();
// XXX: Leaving this intentionally basic for now because invites are about to change totally
+ // FIXME: This comment is now outdated - what do we need to fix? ^
var joinErrorText = this.state.joinError ? "Failed to join room!" : "";
var rejectErrorText = this.state.rejectError ? "Failed to reject invite!" : "";
+
+ // We deliberately don't try to peek into invites, even if we have permission to peek
+ // as they could be a spam vector.
+ // XXX: in future we could give the option of a 'Preview' button which lets them view anyway.
+
return (
-
-
{inviterName} has invited you to a room
-
-
Join
-
Reject
+
+
{joinErrorText}
{rejectErrorText}
+
);
}
@@ -1552,6 +1628,12 @@ module.exports = React.createClass({
);
}
+ else if (this.state.canPeek &&
+ (!myMember || myMember.membership !== "join")) {
+ aux = (
+
+ );
+ }
var conferenceCallNotification = null;
if (this.state.displayConfCallNotification) {
diff --git a/src/components/structures/UserSettings.js b/src/components/structures/UserSettings.js
index 488e7368d1..d1bb66356c 100644
--- a/src/components/structures/UserSettings.js
+++ b/src/components/structures/UserSettings.js
@@ -306,7 +306,7 @@ module.exports = React.createClass({
rowClassName="mx_UserSettings_profileTableRow"
rowLabelClassName="mx_UserSettings_profileLabelCell"
rowInputClassName="mx_UserSettings_profileInputCell"
- buttonClassName="mx_UserSettings_button"
+ buttonClassName="mx_UserSettings_button mx_UserSettings_changePasswordButton"
onError={this.onPasswordChangeError}
onFinished={this.onPasswordChanged} />
);
diff --git a/src/components/views/rooms/EntityTile.js b/src/components/views/rooms/EntityTile.js
index 55629aa8b0..ed0e5cbc41 100644
--- a/src/components/views/rooms/EntityTile.js
+++ b/src/components/views/rooms/EntityTile.js
@@ -72,7 +72,7 @@ module.exports = React.createClass({
},
render: function() {
- var presenceClass = PRESENCE_CLASS[this.props.presenceState];
+ var presenceClass = PRESENCE_CLASS[this.props.presenceState] || "mx_EntityTile_offline";
var mainClassName = "mx_EntityTile ";
mainClassName += presenceClass;
if (this.state.hover) {
@@ -128,10 +128,10 @@ module.exports = React.createClass({
onClick={ this.props.onClick } onMouseEnter={ this.mouseEnter }
onMouseLeave={ this.mouseLeave }>
- {av}
+ { av }
+ { power }
{ nameEl }
- { power }
{ inviteButton }
);
diff --git a/src/components/views/rooms/MemberList.js b/src/components/views/rooms/MemberList.js
index e38d6ae41d..64bebdeca3 100644
--- a/src/components/views/rooms/MemberList.js
+++ b/src/components/views/rooms/MemberList.js
@@ -362,7 +362,7 @@ module.exports = React.createClass({
invitedSection = (
Invited
-
@@ -370,15 +370,15 @@ module.exports = React.createClass({
}
return (
-
{this.inviteTile()}
-
+
{this.makeMemberTiles('join', this.state.searchQuery)}
+ {invitedSection}
+
+
- {invitedSection}
-
);
}
diff --git a/src/components/views/rooms/RoomPreviewBar.js b/src/components/views/rooms/RoomPreviewBar.js
index 2f12c4c8e2..52e6639f13 100644
--- a/src/components/views/rooms/RoomPreviewBar.js
+++ b/src/components/views/rooms/RoomPreviewBar.js
@@ -23,33 +23,60 @@ module.exports = React.createClass({
propTypes: {
onJoinClick: React.PropTypes.func,
- canJoin: React.PropTypes.bool
+ onRejectClick: React.PropTypes.func,
+ inviterName: React.PropTypes.string,
+ canJoin: React.PropTypes.bool,
+ canPreview: React.PropTypes.bool,
},
getDefaultProps: function() {
return {
onJoinClick: function() {},
- canJoin: false
+ canJoin: false,
+ canPreview: true,
};
},
render: function() {
- var joinBlock;
+ var joinBlock, previewBlock;
- if (this.props.canJoin) {
+ if (this.props.inviterName) {
joinBlock = (
-
- Would you like to
join this room?
+
+
+ You have been invited to join this room by { this.props.inviterName }
+
+
+
+ );
+
+ }
+ else if (this.props.canJoin) {
+ joinBlock = (
+
+
+ Would you like to
join this room?
+
+
+ );
+ }
+
+ if (this.props.canPreview) {
+ previewBlock = (
+
+ This is a preview of this room. Room interactions have been disabled.
);
}
return (
-
- This is a preview of this room. Room interactions have been disabled.
+
+ { joinBlock }
+ { previewBlock }
- {joinBlock}
);
}
diff --git a/src/components/views/rooms/RoomSettings.js b/src/components/views/rooms/RoomSettings.js
index 284bee41c2..74b7ba7e7c 100644
--- a/src/components/views/rooms/RoomSettings.js
+++ b/src/components/views/rooms/RoomSettings.js
@@ -295,8 +295,8 @@ module.exports = React.createClass({
else {
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createDialog(ErrorDialog, {
- title: "Invalid alias format",
- description: "'" + alias + "' is not a valid format for an alias",
+ title: "Invalid address format",
+ description: "'" + alias + "' is not a valid format for an address",
});
}
},
@@ -482,11 +482,11 @@ module.exports = React.createClass({
remote_aliases_section =
- This room can be found elsewhere as:
+ Remote addresses for this room:
{ remote_domains.map(function(state_key, i) {
- self.state.aliases[state_key].map(function(alias, j) {
+ return self.state.aliases[state_key].map(function(alias, j) {
return (
);
});
@@ -513,7 +511,7 @@ module.exports = React.createClass({
return
{ alias }
});
})}
-
not set
+
not specified
}
else {
@@ -522,24 +520,26 @@ module.exports = React.createClass({
var aliases_section =
-
Directory
+
Addresses
+
The main address for this room is: { canonical_alias_section }
{ this.state.aliases[domain].length
- ? "This room can be found on " + domain + " as:"
- : "This room is not findable on " + domain }
+ ? "Local addresses for this room:"
+ : "This room has no local addresses" }
{ this.state.aliases[domain].map(function(alias, i) {
var deleteButton;
if (can_set_room_aliases) {
- deleteButton =
;
+ deleteButton =
;
}
return (
-
+
{ remote_aliases_section }
-
The official way to refer to this room is: { canonical_alias_section }
;
var room_colors_section =
@@ -597,23 +597,17 @@ module.exports = React.createClass({
;
var user_levels_section;
- if (user_levels.length) {
+ if (Object.keys(user_levels).length) {
user_levels_section =
-
-
- Users with specific roles are:
-
-
- {Object.keys(user_levels).map(function(user, i) {
- return (
-
- );
- })}
-
-
;
+
+ {Object.keys(user_levels).map(function(user, i) {
+ return (
+
+ { user } is a
+
+ );
+ })}
+ ;
}
else {
user_levels_section =
No users have specific privileges in this room.
@@ -659,7 +653,7 @@ module.exports = React.createClass({
var tags_section =
- This room is tagged as
+ Tagged as:
{ can_set_tag ?
tags.map(function(tag, i) {
return (
@@ -673,25 +667,26 @@ module.exports = React.createClass({
}
+ // FIXME: disable guests_read if the user hasn't turned on shared history
return (