Merge remote-tracking branch 'origin/develop' into dbkr/email_notifs
This commit is contained in:
commit
3381e2b057
36 changed files with 908 additions and 357 deletions
|
@ -67,6 +67,8 @@ module.exports = React.createClass({
|
|||
collapse_rhs: false,
|
||||
ready: false,
|
||||
width: 10000,
|
||||
sideOpacity: 1.0,
|
||||
middleOpacity: 1.0,
|
||||
};
|
||||
if (s.logged_in) {
|
||||
if (MatrixClientPeg.get().getRooms().length) {
|
||||
|
@ -183,6 +185,7 @@ module.exports = React.createClass({
|
|||
},
|
||||
|
||||
componentWillUnmount: function() {
|
||||
this._stopMatrixClient();
|
||||
dis.unregister(this.dispatcherRef);
|
||||
document.removeEventListener("keydown", this.onKeyDown);
|
||||
window.removeEventListener("focus", this.onFocus);
|
||||
|
@ -258,12 +261,7 @@ module.exports = React.createClass({
|
|||
window.localStorage.setItem("mx_hs_url", hsUrl);
|
||||
window.localStorage.setItem("mx_is_url", isUrl);
|
||||
}
|
||||
Notifier.stop();
|
||||
UserActivity.stop();
|
||||
Presence.stop();
|
||||
MatrixClientPeg.get().stopClient();
|
||||
MatrixClientPeg.get().removeAllListeners();
|
||||
MatrixClientPeg.unset();
|
||||
this._stopMatrixClient();
|
||||
this.notifyNewScreen('login');
|
||||
this.replaceState({
|
||||
logged_in: false,
|
||||
|
@ -369,7 +367,7 @@ module.exports = React.createClass({
|
|||
onFinished: function(should_leave) {
|
||||
if (should_leave) {
|
||||
var d = MatrixClientPeg.get().leave(roomId);
|
||||
|
||||
|
||||
// FIXME: controller shouldn't be loading a view :(
|
||||
var Loader = sdk.getComponent("elements.Spinner");
|
||||
var modal = Modal.createDialog(Loader);
|
||||
|
@ -534,6 +532,12 @@ module.exports = React.createClass({
|
|||
collapse_rhs: false,
|
||||
});
|
||||
break;
|
||||
case 'ui_opacity':
|
||||
this.setState({
|
||||
sideOpacity: payload.sideOpacity,
|
||||
middleOpacity: payload.middleOpacity,
|
||||
});
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -596,13 +600,15 @@ module.exports = React.createClass({
|
|||
var theAlias = MatrixTools.getCanonicalAliasForRoom(room);
|
||||
if (theAlias) presentedId = theAlias;
|
||||
|
||||
var color_scheme_event = room.getAccountData("org.matrix.room.color_scheme");
|
||||
var color_scheme = {};
|
||||
if (color_scheme_event) {
|
||||
color_scheme = color_scheme_event.getContent();
|
||||
// XXX: we should validate the event
|
||||
}
|
||||
Tinter.tint(color_scheme.primary_color, color_scheme.secondary_color);
|
||||
// No need to do this given RoomView triggers it itself...
|
||||
// var color_scheme_event = room.getAccountData("org.matrix.room.color_scheme");
|
||||
// var color_scheme = {};
|
||||
// if (color_scheme_event) {
|
||||
// color_scheme = color_scheme_event.getContent();
|
||||
// // XXX: we should validate the event
|
||||
// }
|
||||
// console.log("Tinter.tint from _viewRoom");
|
||||
// Tinter.tint(color_scheme.primary_color, color_scheme.secondary_color);
|
||||
}
|
||||
|
||||
if (eventId) {
|
||||
|
@ -624,10 +630,13 @@ module.exports = React.createClass({
|
|||
if (!this.refs.roomView) {
|
||||
return;
|
||||
}
|
||||
|
||||
var roomview = this.refs.roomView;
|
||||
var roomId = this.refs.roomView.getRoomId();
|
||||
if (!roomId) {
|
||||
return;
|
||||
}
|
||||
var state = roomview.getScrollState();
|
||||
this.scrollStateMap[roomview.props.roomId] = state;
|
||||
this.scrollStateMap[roomId] = state;
|
||||
},
|
||||
|
||||
onLoggedIn: function(credentials) {
|
||||
|
@ -722,6 +731,16 @@ module.exports = React.createClass({
|
|||
});
|
||||
},
|
||||
|
||||
// stop all the background processes related to the current client
|
||||
_stopMatrixClient: function() {
|
||||
Notifier.stop();
|
||||
UserActivity.stop();
|
||||
Presence.stop();
|
||||
MatrixClientPeg.get().stopClient();
|
||||
MatrixClientPeg.get().removeAllListeners();
|
||||
MatrixClientPeg.unset();
|
||||
},
|
||||
|
||||
onKeyDown: function(ev) {
|
||||
/*
|
||||
// Remove this for now as ctrl+alt = alt-gr so this breaks keyboards which rely on alt-gr for numbers
|
||||
|
@ -887,7 +906,7 @@ module.exports = React.createClass({
|
|||
dis.dispatch({
|
||||
action: 'view_user',
|
||||
member: member,
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
onLogoutClick: function(event) {
|
||||
|
@ -1008,6 +1027,7 @@ module.exports = React.createClass({
|
|||
|
||||
onUserSettingsClose: function() {
|
||||
// XXX: use browser history instead to find the previous room?
|
||||
// or maintain a this.state.pageHistory in _setPage()?
|
||||
if (this.state.currentRoom) {
|
||||
dis.dispatch({
|
||||
action: 'view_room',
|
||||
|
@ -1034,7 +1054,7 @@ module.exports = React.createClass({
|
|||
var NewVersionBar = sdk.getComponent('globals.NewVersionBar');
|
||||
var ForgotPassword = sdk.getComponent('structures.login.ForgotPassword');
|
||||
|
||||
// work out the HS URL prompts we should show for
|
||||
// work out the HS URL prompts we should show for
|
||||
|
||||
// needs to be before normal PageTypes as you are logged in technically
|
||||
if (this.state.screen == 'post_registration') {
|
||||
|
@ -1052,29 +1072,29 @@ module.exports = React.createClass({
|
|||
page_element = (
|
||||
<RoomView
|
||||
ref="roomView"
|
||||
roomId={this.state.currentRoom}
|
||||
roomAlias={this.state.currentRoomAlias}
|
||||
roomAddress={this.state.currentRoom || this.state.currentRoomAlias}
|
||||
eventId={this.state.initialEventId}
|
||||
thirdPartyInvite={this.state.thirdPartyInvite}
|
||||
oobData={this.state.roomOobData}
|
||||
highlightedEventId={this.state.highlightedEventId}
|
||||
eventPixelOffset={this.state.initialEventPixelOffset}
|
||||
key={this.state.currentRoom}
|
||||
opacity={this.state.middleOpacity}
|
||||
ConferenceHandler={this.props.ConferenceHandler} />
|
||||
);
|
||||
right_panel = <RightPanel roomId={this.state.currentRoom} collapsed={this.state.collapse_rhs} />
|
||||
right_panel = <RightPanel roomId={this.state.currentRoom} collapsed={this.state.collapse_rhs} opacity={this.state.sideOpacity} />
|
||||
break;
|
||||
case this.PageTypes.UserSettings:
|
||||
page_element = <UserSettings onClose={this.onUserSettingsClose} version={this.state.version} />
|
||||
right_panel = <RightPanel collapsed={this.state.collapse_rhs}/>
|
||||
right_panel = <RightPanel collapsed={this.state.collapse_rhs} opacity={this.state.sideOpacity}/>
|
||||
break;
|
||||
case this.PageTypes.CreateRoom:
|
||||
page_element = <CreateRoom onRoomCreated={this.onRoomCreated}/>
|
||||
right_panel = <RightPanel collapsed={this.state.collapse_rhs}/>
|
||||
right_panel = <RightPanel collapsed={this.state.collapse_rhs} opacity={this.state.sideOpacity}/>
|
||||
break;
|
||||
case this.PageTypes.RoomDirectory:
|
||||
page_element = <RoomDirectory />
|
||||
right_panel = <RightPanel collapsed={this.state.collapse_rhs}/>
|
||||
right_panel = <RightPanel collapsed={this.state.collapse_rhs} opacity={this.state.sideOpacity}/>
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -1098,7 +1118,7 @@ module.exports = React.createClass({
|
|||
<div className="mx_MatrixChat_wrapper">
|
||||
{topBar}
|
||||
<div className={bodyClasses}>
|
||||
<LeftPanel selectedRoom={this.state.currentRoom} collapsed={this.state.collapse_lhs} />
|
||||
<LeftPanel selectedRoom={this.state.currentRoom} collapsed={this.state.collapse_lhs} opacity={this.state.sideOpacity}/>
|
||||
<main className="mx_MatrixChat_middlePanel">
|
||||
{page_element}
|
||||
</main>
|
||||
|
|
|
@ -19,6 +19,8 @@ var ReactDOM = require("react-dom");
|
|||
var dis = require("../../dispatcher");
|
||||
var sdk = require('../../index');
|
||||
|
||||
var MatrixClientPeg = require('../../MatrixClientPeg')
|
||||
|
||||
/* (almost) stateless UI component which builds the event tiles in the room timeline.
|
||||
*/
|
||||
module.exports = React.createClass({
|
||||
|
@ -65,6 +67,9 @@ module.exports = React.createClass({
|
|||
|
||||
// callback which is called when more content is needed.
|
||||
onFillRequest: React.PropTypes.func,
|
||||
|
||||
// opacity for dynamic UI fading effects
|
||||
opacity: React.PropTypes.number,
|
||||
},
|
||||
|
||||
componentWillMount: function() {
|
||||
|
@ -147,7 +152,7 @@ module.exports = React.createClass({
|
|||
this.refs.scrollPanel.scrollToBottom();
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Page up/down.
|
||||
*
|
||||
|
@ -332,13 +337,17 @@ module.exports = React.createClass({
|
|||
// Local echos have a send "status".
|
||||
var scrollToken = mxEv.status ? undefined : eventId;
|
||||
|
||||
var readReceipts = this._getReadReceiptsForEvent(mxEv);
|
||||
|
||||
ret.push(
|
||||
<li key={eventId}
|
||||
ref={this._collectEventNode.bind(this, eventId)}
|
||||
data-scroll-token={scrollToken}>
|
||||
<EventTile mxEvent={mxEv} continuation={continuation}
|
||||
onWidgetLoad={this._onWidgetLoad}
|
||||
last={last} isSelectedEvent={highlight} />
|
||||
readReceipts={readReceipts}
|
||||
eventSendStatus={mxEv.status}
|
||||
last={last} isSelectedEvent={highlight}/>
|
||||
</li>
|
||||
);
|
||||
|
||||
|
@ -356,6 +365,30 @@ module.exports = React.createClass({
|
|||
!== new Date(nextEventTs).toDateString());
|
||||
},
|
||||
|
||||
// get a list of the userids whose read receipts should
|
||||
// be shown next to this event
|
||||
_getReadReceiptsForEvent: function(event) {
|
||||
var myUserId = MatrixClientPeg.get().credentials.userId;
|
||||
|
||||
// get list of read receipts, sorted most recent first
|
||||
var room = MatrixClientPeg.get().getRoom(event.getRoomId());
|
||||
if (!room) {
|
||||
// huh.
|
||||
return null;
|
||||
}
|
||||
|
||||
return room.getReceiptsForEvent(event).filter(function(r) {
|
||||
return r.type === "m.read" && r.userId != myUserId;
|
||||
}).sort(function(r1, r2) {
|
||||
return r2.data.ts - r1.data.ts;
|
||||
}).map(function(r) {
|
||||
return room.getMember(r.userId);
|
||||
}).filter(function(m) {
|
||||
// check that the user is a known room member
|
||||
return m;
|
||||
});
|
||||
},
|
||||
|
||||
_getReadMarkerTile: function(visible) {
|
||||
var hr;
|
||||
if (visible) {
|
||||
|
@ -423,12 +456,15 @@ module.exports = React.createClass({
|
|||
bottomSpinner = <li key="_bottomSpinner"><Spinner /></li>;
|
||||
}
|
||||
|
||||
var style = this.props.hidden ? { display: 'none' } : {};
|
||||
style.opacity = this.props.opacity;
|
||||
|
||||
return (
|
||||
<ScrollPanel ref="scrollPanel" className="mx_RoomView_messagePanel"
|
||||
onScroll={ this.props.onScroll }
|
||||
<ScrollPanel ref="scrollPanel" className="mx_RoomView_messagePanel mx_fadable"
|
||||
onScroll={ this.props.onScroll }
|
||||
onResize={ this.onResize }
|
||||
onFillRequest={ this.props.onFillRequest }
|
||||
style={ this.props.hidden ? { display: 'none' } : {} }
|
||||
style={ style }
|
||||
stickyBottom={ this.props.stickyBottom }>
|
||||
{topSpinner}
|
||||
{this._getEventTiles()}
|
||||
|
|
|
@ -54,11 +54,15 @@ module.exports = React.createClass({
|
|||
propTypes: {
|
||||
ConferenceHandler: React.PropTypes.any,
|
||||
|
||||
roomId: React.PropTypes.string.isRequired,
|
||||
|
||||
// if we are referring to this room by a given alias (e.g. in the URL), track it.
|
||||
// useful for joining rooms by alias correctly (and fixing https://github.com/vector-im/vector-web/issues/819)
|
||||
roomAlias: React.PropTypes.string,
|
||||
// the ID for this room (or, if we don't know it, an alias for it)
|
||||
//
|
||||
// XXX: if this is an alias, we will display a 'join' dialogue,
|
||||
// regardless of whether we are already a member, or if the room is
|
||||
// peekable. Currently there is a big mess, where at least four
|
||||
// different components (RoomView, MatrixChat, RoomDirectory,
|
||||
// SlashCommands) have logic for turning aliases into rooms, and each
|
||||
// of them do it differently and have different edge cases.
|
||||
roomAddress: React.PropTypes.string.isRequired,
|
||||
|
||||
// An object representing a third party invite to join this room
|
||||
// Fields:
|
||||
|
@ -90,10 +94,13 @@ module.exports = React.createClass({
|
|||
// ID of an event to highlight. If undefined, no event will be highlighted.
|
||||
// Typically this will either be the same as 'eventId', or undefined.
|
||||
highlightedEventId: React.PropTypes.string,
|
||||
|
||||
// is the RightPanel collapsed?
|
||||
rightPanelCollapsed: React.PropTypes.bool,
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
var room = this.props.roomId ? MatrixClientPeg.get().getRoom(this.props.roomId) : null;
|
||||
var room = MatrixClientPeg.get().getRoom(this.props.roomAddress);
|
||||
return {
|
||||
room: room,
|
||||
roomLoading: !room,
|
||||
|
@ -123,7 +130,6 @@ module.exports = React.createClass({
|
|||
this.dispatcherRef = dis.register(this.onAction);
|
||||
MatrixClientPeg.get().on("Room", this.onRoom);
|
||||
MatrixClientPeg.get().on("Room.timeline", this.onRoomTimeline);
|
||||
MatrixClientPeg.get().on("Room.name", this.onRoomName);
|
||||
MatrixClientPeg.get().on("Room.accountData", this.onRoomAccountData);
|
||||
MatrixClientPeg.get().on("RoomState.members", this.onRoomStateMember);
|
||||
// xchat-style tab complete, add a colon if tab
|
||||
|
@ -146,9 +152,9 @@ 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);
|
||||
console.log("Attempting to peek into room %s", this.props.roomAddress);
|
||||
|
||||
MatrixClientPeg.get().peekInRoom(this.props.roomId).then((room) => {
|
||||
MatrixClientPeg.get().peekInRoom(this.props.roomAddress).then((room) => {
|
||||
this.setState({
|
||||
room: room,
|
||||
roomLoading: false,
|
||||
|
@ -200,14 +206,15 @@ module.exports = React.createClass({
|
|||
if (MatrixClientPeg.get()) {
|
||||
MatrixClientPeg.get().removeListener("Room", this.onRoom);
|
||||
MatrixClientPeg.get().removeListener("Room.timeline", this.onRoomTimeline);
|
||||
MatrixClientPeg.get().removeListener("Room.name", this.onRoomName);
|
||||
MatrixClientPeg.get().removeListener("Room.accountData", this.onRoomAccountData);
|
||||
MatrixClientPeg.get().removeListener("RoomState.members", this.onRoomStateMember);
|
||||
}
|
||||
|
||||
window.removeEventListener('resize', this.onResize);
|
||||
|
||||
Tinter.tint(); // reset colourscheme
|
||||
// no need to do this as Dir & Settings are now overlays. It just burnt CPU.
|
||||
// console.log("Tinter.tint from RoomView.unmount");
|
||||
// Tinter.tint(); // reset colourscheme
|
||||
},
|
||||
|
||||
onAction: function(payload) {
|
||||
|
@ -233,7 +240,7 @@ module.exports = React.createClass({
|
|||
return;
|
||||
}
|
||||
|
||||
var call = CallHandler.getCallForRoom(payload.room_id);
|
||||
var call = this._getCallForRoom();
|
||||
var callState;
|
||||
|
||||
if (call) {
|
||||
|
@ -256,7 +263,7 @@ module.exports = React.createClass({
|
|||
},
|
||||
|
||||
componentWillReceiveProps: function(newProps) {
|
||||
if (newProps.roomId != this.props.roomId) {
|
||||
if (newProps.roomAddress != this.props.roomAddress) {
|
||||
throw new Error("changing room on a RoomView is not supported");
|
||||
}
|
||||
|
||||
|
@ -270,7 +277,7 @@ module.exports = React.createClass({
|
|||
if (this.unmounted) return;
|
||||
|
||||
// ignore events for other rooms
|
||||
if (room.roomId != this.props.roomId) return;
|
||||
if (!this.state.room || room.roomId != this.state.room.roomId) return;
|
||||
|
||||
// ignore anything but real-time updates at the end of the room:
|
||||
// updates from pagination will happen when the paginate completes.
|
||||
|
@ -286,7 +293,7 @@ module.exports = React.createClass({
|
|||
// no change
|
||||
}
|
||||
else {
|
||||
this.setState((state, props) => {
|
||||
this.setState((state, props) => {
|
||||
return {numUnreadMessages: state.numUnreadMessages + 1};
|
||||
});
|
||||
}
|
||||
|
@ -321,30 +328,18 @@ module.exports = React.createClass({
|
|||
// set it in our state and start using it (ie. init the timeline)
|
||||
// This will happen if we start off viewing a room we're not joined,
|
||||
// then join it whilst RoomView is looking at that room.
|
||||
if (room.roomId == this.props.roomId && !this.state.room) {
|
||||
if (!this.state.room && room.roomId == this._joiningRoomId) {
|
||||
this._joiningRoomId = undefined;
|
||||
this.setState({
|
||||
room: room
|
||||
room: room,
|
||||
joining: false,
|
||||
});
|
||||
this._onRoomLoaded(room);
|
||||
}
|
||||
},
|
||||
|
||||
onRoomName: function(room) {
|
||||
// NB don't set state.room here.
|
||||
//
|
||||
// When peeking, this event lands *before* the timeline is correctly
|
||||
// synced; if we set state.room here, the TimelinePanel will be
|
||||
// instantiated, and it will initialise its scroll state, with *no
|
||||
// events*. In short, the scroll state will be all messed up.
|
||||
//
|
||||
// There's no need to set state.room here anyway.
|
||||
if (room.roomId == this.props.roomId) {
|
||||
this.forceUpdate();
|
||||
}
|
||||
},
|
||||
|
||||
updateTint: function() {
|
||||
var room = MatrixClientPeg.get().getRoom(this.props.roomId);
|
||||
var room = this.state.room;
|
||||
if (!room) return;
|
||||
|
||||
var color_scheme_event = room.getAccountData("org.matrix.room.color_scheme");
|
||||
|
@ -352,7 +347,8 @@ module.exports = React.createClass({
|
|||
if (color_scheme_event) {
|
||||
color_scheme = color_scheme_event.getContent();
|
||||
// XXX: we should validate the event
|
||||
}
|
||||
}
|
||||
console.log("Tinter.tint from updateTint");
|
||||
Tinter.tint(color_scheme.primary_color, color_scheme.secondary_color);
|
||||
},
|
||||
|
||||
|
@ -361,34 +357,40 @@ module.exports = React.createClass({
|
|||
if (event.getType === "org.matrix.room.color_scheme") {
|
||||
var color_scheme = event.getContent();
|
||||
// XXX: we should validate the event
|
||||
console.log("Tinter.tint from onRoomAccountData");
|
||||
Tinter.tint(color_scheme.primary_color, color_scheme.secondary_color);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
onRoomStateMember: function(ev, state, member) {
|
||||
if (member.roomId === this.props.roomId) {
|
||||
// a member state changed in this room, refresh the tab complete list
|
||||
this._updateTabCompleteList();
|
||||
|
||||
var room = MatrixClientPeg.get().getRoom(this.props.roomId);
|
||||
if (!room) return;
|
||||
var me = MatrixClientPeg.get().credentials.userId;
|
||||
if (this.state.joining && room.hasMembershipState(me, "join")) {
|
||||
this.setState({
|
||||
joining: false
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (!this.props.ConferenceHandler) {
|
||||
// ignore if we don't have a room yet
|
||||
if (!this.state.room) {
|
||||
return;
|
||||
}
|
||||
if (member.roomId !== this.props.roomId ||
|
||||
member.userId !== this.props.ConferenceHandler.getConferenceUserIdForRoom(member.roomId)) {
|
||||
|
||||
// ignore members in other rooms
|
||||
if (member.roomId !== this.state.room.roomId) {
|
||||
return;
|
||||
}
|
||||
this._updateConfCallNotification();
|
||||
|
||||
// a member state changed in this room, refresh the tab complete list
|
||||
this._updateTabCompleteList();
|
||||
|
||||
// if we are now a member of the room, where we were not before, that
|
||||
// means we have finished joining a room we were previously peeking
|
||||
// into.
|
||||
var me = MatrixClientPeg.get().credentials.userId;
|
||||
if (this.state.joining && this.state.room.hasMembershipState(me, "join")) {
|
||||
this.setState({
|
||||
joining: false
|
||||
});
|
||||
}
|
||||
|
||||
if (this.props.ConferenceHandler &&
|
||||
member.userId === this.props.ConferenceHandler.getConferenceUserIdForRoom(member.roomId)) {
|
||||
this._updateConfCallNotification();
|
||||
}
|
||||
},
|
||||
|
||||
_hasUnsentMessages: function(room) {
|
||||
|
@ -403,12 +405,12 @@ module.exports = React.createClass({
|
|||
},
|
||||
|
||||
_updateConfCallNotification: function() {
|
||||
var room = MatrixClientPeg.get().getRoom(this.props.roomId);
|
||||
var room = this.state.room;
|
||||
if (!room || !this.props.ConferenceHandler) {
|
||||
return;
|
||||
}
|
||||
var confMember = room.getMember(
|
||||
this.props.ConferenceHandler.getConferenceUserIdForRoom(this.props.roomId)
|
||||
this.props.ConferenceHandler.getConferenceUserIdForRoom(room.roomId)
|
||||
);
|
||||
|
||||
if (!confMember) {
|
||||
|
@ -427,7 +429,7 @@ module.exports = React.createClass({
|
|||
},
|
||||
|
||||
componentDidMount: function() {
|
||||
var call = CallHandler.getCallForRoom(this.props.roomId);
|
||||
var call = this._getCallForRoom();
|
||||
var callState = call ? call.call_state : "ended";
|
||||
this.setState({
|
||||
callState: callState
|
||||
|
@ -559,25 +561,35 @@ module.exports = React.createClass({
|
|||
|
||||
display_name_promise.then(() => {
|
||||
var sign_url = this.props.thirdPartyInvite ? this.props.thirdPartyInvite.inviteSignUrl : undefined;
|
||||
return MatrixClientPeg.get().joinRoom(this.props.roomAlias || this.props.roomId,
|
||||
return MatrixClientPeg.get().joinRoom(this.props.roomAddress,
|
||||
{ inviteSignUrl: sign_url } )
|
||||
}).done(function() {
|
||||
}).then(function(resp) {
|
||||
var roomId = resp.roomId;
|
||||
|
||||
// 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.
|
||||
// joining flag set until we see it.
|
||||
|
||||
// We'll need to initialise the timeline when joining, but due to
|
||||
// the above, we can't do it here: we do it in onRoom instead,
|
||||
// once we have a useable room object.
|
||||
var room = MatrixClientPeg.get().getRoom(self.props.roomId);
|
||||
var me = MatrixClientPeg.get().credentials.userId;
|
||||
self.setState({
|
||||
joining: room ? !room.hasMembershipState(me, "join") : true,
|
||||
room: room
|
||||
});
|
||||
}, function(error) {
|
||||
var room = MatrixClientPeg.get().getRoom(roomId);
|
||||
if (!room) {
|
||||
// wait for the room to turn up in onRoom.
|
||||
self._joiningRoomId = roomId;
|
||||
} else {
|
||||
// we've got a valid room, but that might also just mean that
|
||||
// it was peekable (so we had one before anyway). If we are
|
||||
// not yet a member of the room, we will need to wait for that
|
||||
// to happen, in onRoomStateMember.
|
||||
var me = MatrixClientPeg.get().credentials.userId;
|
||||
self.setState({
|
||||
joining: !room.hasMembershipState(me, "join"),
|
||||
room: room
|
||||
});
|
||||
}
|
||||
}).catch(function(error) {
|
||||
self.setState({
|
||||
joining: false,
|
||||
joinError: error
|
||||
|
@ -612,7 +624,8 @@ module.exports = React.createClass({
|
|||
description: msg
|
||||
});
|
||||
}
|
||||
});
|
||||
}).done();
|
||||
|
||||
this.setState({
|
||||
joining: true
|
||||
});
|
||||
|
@ -667,7 +680,7 @@ module.exports = React.createClass({
|
|||
uploadFile: function(file) {
|
||||
var self = this;
|
||||
ContentMessages.sendContentToRoom(
|
||||
file, this.props.roomId, MatrixClientPeg.get()
|
||||
file, this.state.room.roomId, MatrixClientPeg.get()
|
||||
).done(undefined, function(error) {
|
||||
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
|
@ -702,7 +715,7 @@ module.exports = React.createClass({
|
|||
filter = {
|
||||
// XXX: it's unintuitive that the filter for searching doesn't have the same shape as the v2 filter API :(
|
||||
rooms: [
|
||||
this.props.roomId
|
||||
this.state.room.roomId
|
||||
]
|
||||
};
|
||||
}
|
||||
|
@ -860,6 +873,8 @@ module.exports = React.createClass({
|
|||
},
|
||||
|
||||
onSettingsSaveClick: function() {
|
||||
if (!this.refs.room_settings) return;
|
||||
|
||||
this.setState({
|
||||
uploadingRoomSettings: true,
|
||||
});
|
||||
|
@ -901,6 +916,7 @@ module.exports = React.createClass({
|
|||
},
|
||||
|
||||
onCancelClick: function() {
|
||||
console.log("updateTint from onCancelClick");
|
||||
this.updateTint();
|
||||
this.setState({editingRoomSettings: false});
|
||||
},
|
||||
|
@ -908,12 +924,12 @@ module.exports = React.createClass({
|
|||
onLeaveClick: function() {
|
||||
dis.dispatch({
|
||||
action: 'leave_room',
|
||||
room_id: this.props.roomId,
|
||||
room_id: this.state.room.roomId,
|
||||
});
|
||||
},
|
||||
|
||||
onForgetClick: function() {
|
||||
MatrixClientPeg.get().forget(this.props.roomId).done(function() {
|
||||
MatrixClientPeg.get().forget(this.state.room.roomId).done(function() {
|
||||
dis.dispatch({ action: 'view_next_room' });
|
||||
}, function(err) {
|
||||
var errCode = err.errcode || "unknown error code";
|
||||
|
@ -930,7 +946,7 @@ module.exports = React.createClass({
|
|||
this.setState({
|
||||
rejecting: true
|
||||
});
|
||||
MatrixClientPeg.get().leave(this.props.roomId).done(function() {
|
||||
MatrixClientPeg.get().leave(this.props.roomAddress).done(function() {
|
||||
dis.dispatch({ action: 'view_next_room' });
|
||||
self.setState({
|
||||
rejecting: false
|
||||
|
@ -984,7 +1000,8 @@ module.exports = React.createClass({
|
|||
},
|
||||
|
||||
// update the read marker to match the read-receipt
|
||||
forgetReadMarker: function() {
|
||||
forgetReadMarker: function(ev) {
|
||||
ev.stopPropagation();
|
||||
this.refs.messagePanel.forgetReadMarker();
|
||||
},
|
||||
|
||||
|
@ -1087,7 +1104,7 @@ module.exports = React.createClass({
|
|||
},
|
||||
|
||||
onMuteAudioClick: function() {
|
||||
var call = CallHandler.getCallForRoom(this.props.roomId);
|
||||
var call = this._getCallForRoom();
|
||||
if (!call) {
|
||||
return;
|
||||
}
|
||||
|
@ -1099,7 +1116,7 @@ module.exports = React.createClass({
|
|||
},
|
||||
|
||||
onMuteVideoClick: function() {
|
||||
var call = CallHandler.getCallForRoom(this.props.roomId);
|
||||
var call = this._getCallForRoom();
|
||||
if (!call) {
|
||||
return;
|
||||
}
|
||||
|
@ -1139,11 +1156,35 @@ module.exports = React.createClass({
|
|||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the ID of the displayed room
|
||||
*
|
||||
* Returns null if the RoomView was instantiated on a room alias and
|
||||
* we haven't yet joined the room.
|
||||
*/
|
||||
getRoomId: function() {
|
||||
if (!this.state.room) {
|
||||
return null;
|
||||
}
|
||||
return this.state.room.roomId;
|
||||
},
|
||||
|
||||
/**
|
||||
* get any current call for this room
|
||||
*/
|
||||
_getCallForRoom: function() {
|
||||
if (!this.state.room) {
|
||||
return null;
|
||||
}
|
||||
return CallHandler.getCallForRoom(this.state.room.roomId);
|
||||
},
|
||||
|
||||
// this has to be a proper method rather than an unnamed function,
|
||||
// otherwise react calls it with null on each update.
|
||||
_gatherTimelinePanelRef: function(r) {
|
||||
this.refs.messagePanel = r;
|
||||
if(r) {
|
||||
console.log("updateTint from RoomView._gatherTimelinePanelRef");
|
||||
this.updateTint();
|
||||
}
|
||||
},
|
||||
|
@ -1161,13 +1202,12 @@ module.exports = React.createClass({
|
|||
var TimelinePanel = sdk.getComponent("structures.TimelinePanel");
|
||||
|
||||
if (!this.state.room) {
|
||||
if (this.props.roomId) {
|
||||
if (this.state.roomLoading) {
|
||||
return (
|
||||
<div className="mx_RoomView">
|
||||
<Loader />
|
||||
</div>
|
||||
);
|
||||
);
|
||||
}
|
||||
else {
|
||||
var inviterName = undefined;
|
||||
|
@ -1183,7 +1223,11 @@ module.exports = React.createClass({
|
|||
// We've got to this room by following a link, possibly a third party invite.
|
||||
return (
|
||||
<div className="mx_RoomView">
|
||||
<RoomHeader ref="header" room={this.state.room} oobData={this.props.oobData} />
|
||||
<RoomHeader ref="header"
|
||||
room={this.state.room}
|
||||
oobData={this.props.oobData}
|
||||
rightPanelCollapsed={ this.props.rightPanelCollapsed }
|
||||
/>
|
||||
<div className="mx_RoomView_auxPanel">
|
||||
<RoomPreviewBar onJoinClick={ this.onJoinButtonClicked }
|
||||
onRejectClick={ this.onRejectThreepidInviteButtonClicked }
|
||||
|
@ -1196,14 +1240,8 @@ module.exports = React.createClass({
|
|||
</div>
|
||||
<div className="mx_RoomView_messagePanel"></div>
|
||||
</div>
|
||||
);
|
||||
);
|
||||
}
|
||||
}
|
||||
else {
|
||||
return (
|
||||
<div />
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
var myUserId = MatrixClientPeg.get().credentials.userId;
|
||||
|
@ -1233,7 +1271,7 @@ module.exports = React.createClass({
|
|||
inviterName={ inviterName }
|
||||
canJoin={ true } canPreview={ false }
|
||||
spinner={this.state.joining}
|
||||
room={this.state.room}
|
||||
room={this.state.room}
|
||||
/>
|
||||
</div>
|
||||
<div className="mx_RoomView_messagePanel"></div>
|
||||
|
@ -1245,7 +1283,7 @@ module.exports = React.createClass({
|
|||
// We have successfully loaded this room, and are not previewing.
|
||||
// Display the "normal" room view.
|
||||
|
||||
var call = CallHandler.getCallForRoom(this.props.roomId);
|
||||
var call = this._getCallForRoom();
|
||||
var inCall = false;
|
||||
if (call && (this.state.callState !== 'ended' && this.state.callState !== 'ringing')) {
|
||||
inCall = true;
|
||||
|
@ -1257,13 +1295,6 @@ module.exports = React.createClass({
|
|||
|
||||
var statusBar;
|
||||
|
||||
// for testing UI...
|
||||
// this.state.upload = {
|
||||
// uploadedBytes: 123493,
|
||||
// totalBytes: 347534,
|
||||
// fileName: "testing_fooble.jpg",
|
||||
// }
|
||||
|
||||
if (ContentMessages.getCurrentUploads().length > 0) {
|
||||
var UploadBar = sdk.getComponent('structures.UploadBar');
|
||||
statusBar = <UploadBar room={this.state.room} />
|
||||
|
@ -1314,7 +1345,7 @@ module.exports = React.createClass({
|
|||
inviterName={inviterName}
|
||||
invitedEmail={invitedEmail}
|
||||
canPreview={this.state.canPeek}
|
||||
room={this.state.room}
|
||||
room={this.state.room}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@ -1339,7 +1370,7 @@ module.exports = React.createClass({
|
|||
messageComposer =
|
||||
<MessageComposer
|
||||
room={this.state.room} onResize={this.onChildResize} uploadFile={this.uploadFile}
|
||||
callState={this.state.callState} tabComplete={this.tabComplete} />
|
||||
callState={this.state.callState} tabComplete={this.tabComplete} opacity={ this.props.opacity }/>
|
||||
}
|
||||
|
||||
// TODO: Why aren't we storing the term/scope/count in this format
|
||||
|
@ -1394,8 +1425,12 @@ module.exports = React.createClass({
|
|||
|
||||
if (this.state.searchResults) {
|
||||
searchResultsPanel = (
|
||||
<ScrollPanel ref="searchResultsPanel" className="mx_RoomView_messagePanel mx_RoomView_searchResultsPanel"
|
||||
onFillRequest={ this.onSearchResultsFillRequest } onResize={ this.onSearchResultsResize }>
|
||||
<ScrollPanel ref="searchResultsPanel"
|
||||
className="mx_RoomView_messagePanel mx_RoomView_searchResultsPanel"
|
||||
onFillRequest={ this.onSearchResultsFillRequest }
|
||||
onResize={ this.onSearchResultsResize }
|
||||
style={{ opacity: this.props.opacity }}
|
||||
>
|
||||
<li className={scrollheader_classes}></li>
|
||||
{this.getSearchResultTiles()}
|
||||
</ScrollPanel>
|
||||
|
@ -1412,13 +1447,14 @@ module.exports = React.createClass({
|
|||
eventPixelOffset={this.props.eventPixelOffset}
|
||||
onScroll={ this.onMessageListScroll }
|
||||
onReadMarkerUpdated={ this._updateTopUnreadMessagesBar }
|
||||
opacity={ this.props.opacity }
|
||||
/>);
|
||||
|
||||
var topUnreadMessagesBar = null;
|
||||
if (this.state.showTopUnreadMessagesBar) {
|
||||
var TopUnreadMessagesBar = sdk.getComponent('rooms.TopUnreadMessagesBar');
|
||||
topUnreadMessagesBar = (
|
||||
<div className="mx_RoomView_topUnreadMessagesBar">
|
||||
<div className="mx_RoomView_topUnreadMessagesBar mx_fadable" style={{ opacity: this.props.opacity }}>
|
||||
<TopUnreadMessagesBar
|
||||
onScrollUpClick={this.jumpToReadMarker}
|
||||
onCloseClick={this.forgetReadMarker}
|
||||
|
@ -1432,6 +1468,7 @@ module.exports = React.createClass({
|
|||
<RoomHeader ref="header" room={this.state.room} searchInfo={searchInfo}
|
||||
oobData={this.props.oobData}
|
||||
editing={this.state.editingRoomSettings}
|
||||
saving={this.state.uploadingRoomSettings}
|
||||
onSearchClick={this.onSearchClick}
|
||||
onSettingsClick={this.onSettingsClick}
|
||||
onSaveClick={this.onSettingsSaveClick}
|
||||
|
@ -1446,7 +1483,7 @@ module.exports = React.createClass({
|
|||
{ topUnreadMessagesBar }
|
||||
{ messagePanel }
|
||||
{ searchResultsPanel }
|
||||
<div className="mx_RoomView_statusArea">
|
||||
<div className="mx_RoomView_statusArea mx_fadable" style={{ opacity: this.props.opacity }}>
|
||||
<div className="mx_RoomView_statusAreaBox">
|
||||
<div className="mx_RoomView_statusAreaBox_line"></div>
|
||||
{ statusBar }
|
||||
|
|
|
@ -540,6 +540,7 @@ module.exports = React.createClass({
|
|||
// it's not obvious why we have a separate div and ol anyway.
|
||||
return (<GeminiScrollbar autoshow={true} ref="geminiPanel"
|
||||
onScroll={this.onScroll} onResize={this.onResize}
|
||||
relayoutOnUpdate={false}
|
||||
className={this.props.className} style={this.props.style}>
|
||||
<div className="mx_RoomView_messageListWrapper">
|
||||
<ol ref="itemlist" className="mx_RoomView_MessageList" aria-live="polite">
|
||||
|
|
|
@ -76,6 +76,9 @@ var TimelinePanel = React.createClass({
|
|||
|
||||
// callback which is called when the read-up-to mark is updated.
|
||||
onReadMarkerUpdated: React.PropTypes.func,
|
||||
|
||||
// opacity for dynamic UI fading effects
|
||||
opacity: React.PropTypes.number,
|
||||
},
|
||||
|
||||
statics: {
|
||||
|
@ -172,8 +175,27 @@ var TimelinePanel = React.createClass({
|
|||
},
|
||||
|
||||
shouldComponentUpdate: function(nextProps, nextState) {
|
||||
return (!ObjectUtils.shallowEqual(this.props, nextProps) ||
|
||||
!ObjectUtils.shallowEqual(this.state, nextState));
|
||||
if (!ObjectUtils.shallowEqual(this.props, nextProps)) {
|
||||
if (DEBUG) {
|
||||
console.group("Timeline.shouldComponentUpdate: props change");
|
||||
console.log("props before:", this.props);
|
||||
console.log("props after:", nextProps);
|
||||
console.groupEnd();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!ObjectUtils.shallowEqual(this.state, nextState)) {
|
||||
if (DEBUG) {
|
||||
console.group("Timeline.shouldComponentUpdate: state change");
|
||||
console.log("state before:", this.state);
|
||||
console.log("state after:", nextState);
|
||||
console.groupEnd();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
|
||||
componentWillUnmount: function() {
|
||||
|
@ -222,8 +244,8 @@ var TimelinePanel = React.createClass({
|
|||
this.setState({
|
||||
[paginatingKey]: false,
|
||||
[canPaginateKey]: r,
|
||||
events: this._getEvents(),
|
||||
});
|
||||
this._reloadEvents();
|
||||
return r;
|
||||
});
|
||||
},
|
||||
|
@ -264,25 +286,14 @@ var TimelinePanel = React.createClass({
|
|||
// updates from pagination will happen when the paginate completes.
|
||||
if (toStartOfTimeline || !data || !data.liveEvent) return;
|
||||
|
||||
// even if we previously gave up forward-paginating, it's worth
|
||||
// having another go now.
|
||||
this.setState({canForwardPaginate: true});
|
||||
|
||||
if (!this.refs.messagePanel) return;
|
||||
|
||||
if (!this.refs.messagePanel.getScrollState().stuckAtBottom) return;
|
||||
|
||||
// when a new event arrives when the user is not watching the window, but the
|
||||
// window is in its auto-scroll mode, make sure the read marker is visible.
|
||||
//
|
||||
// We ignore events we have sent ourselves; we don't want to see the
|
||||
// read-marker when a remote echo of an event we have just sent takes
|
||||
// more than the timeout on userCurrentlyActive.
|
||||
//
|
||||
var myUserId = MatrixClientPeg.get().credentials.userId;
|
||||
var sender = ev.sender ? ev.sender.userId : null;
|
||||
if (sender != myUserId && !UserActivity.userCurrentlyActive()) {
|
||||
this.setState({readMarkerVisible: true});
|
||||
if (!this.refs.messagePanel.getScrollState().stuckAtBottom) {
|
||||
// we won't load this event now, because we don't want to push any
|
||||
// events off the other end of the timeline. But we need to note
|
||||
// that we can now paginate.
|
||||
this.setState({canForwardPaginate: true});
|
||||
return;
|
||||
}
|
||||
|
||||
// tell the timeline window to try to advance itself, but not to make
|
||||
|
@ -291,11 +302,46 @@ var TimelinePanel = React.createClass({
|
|||
// we deliberately avoid going via the ScrollPanel for this call - the
|
||||
// ScrollPanel might already have an active pagination promise, which
|
||||
// will fail, but would stop us passing the pagination request to the
|
||||
// timeline window.
|
||||
// timeline window.
|
||||
//
|
||||
// see https://github.com/vector-im/vector-web/issues/1035
|
||||
this._timelineWindow.paginate(EventTimeline.FORWARDS, 1, false)
|
||||
.done(this._reloadEvents);
|
||||
this._timelineWindow.paginate(EventTimeline.FORWARDS, 1, false).done(() => {
|
||||
if (this.unmounted) { return; }
|
||||
|
||||
var events = this._timelineWindow.getEvents();
|
||||
var lastEv = events[events.length-1];
|
||||
|
||||
// if we're at the end of the live timeline, append the pending events
|
||||
if (!this._timelineWindow.canPaginate(EventTimeline.FORWARDS)) {
|
||||
events.push(... this.props.room.getPendingEvents());
|
||||
}
|
||||
|
||||
var updatedState = {events: events};
|
||||
|
||||
// when a new event arrives when the user is not watching the
|
||||
// window, but the window is in its auto-scroll mode, make sure the
|
||||
// read marker is visible.
|
||||
//
|
||||
// We ignore events we have sent ourselves; we don't want to see the
|
||||
// read-marker when a remote echo of an event we have just sent takes
|
||||
// more than the timeout on userCurrentlyActive.
|
||||
//
|
||||
var myUserId = MatrixClientPeg.get().credentials.userId;
|
||||
var sender = ev.sender ? ev.sender.userId : null;
|
||||
var callback = null;
|
||||
if (sender != myUserId && !UserActivity.userCurrentlyActive()) {
|
||||
updatedState.readMarkerVisible = true;
|
||||
} else if(lastEv && this.getReadMarkerPosition() === 0) {
|
||||
// we know we're stuckAtBottom, so we can advance the RM
|
||||
// immediately, to save a later render cycle
|
||||
this._setReadMarker(lastEv.getId(), lastEv.getTs(), true);
|
||||
updatedState.readMarkerVisible = false;
|
||||
updatedState.readMarkerEventId = lastEv.getId();
|
||||
callback = this.props.onReadMarkerUpdated;
|
||||
}
|
||||
|
||||
this.setState(updatedState, callback);
|
||||
});
|
||||
},
|
||||
|
||||
onRoomTimelineReset: function(room) {
|
||||
|
@ -717,6 +763,13 @@ var TimelinePanel = React.createClass({
|
|||
// the results if so.
|
||||
if (this.unmounted) return;
|
||||
|
||||
this.setState({
|
||||
events: this._getEvents(),
|
||||
});
|
||||
},
|
||||
|
||||
// get the list of events from the timeline window and the pending event list
|
||||
_getEvents: function() {
|
||||
var events = this._timelineWindow.getEvents();
|
||||
|
||||
// if we're at the end of the live timeline, append the pending events
|
||||
|
@ -724,9 +777,7 @@ var TimelinePanel = React.createClass({
|
|||
events.push(... this.props.room.getPendingEvents());
|
||||
}
|
||||
|
||||
this.setState({
|
||||
events: events,
|
||||
});
|
||||
return events;
|
||||
},
|
||||
|
||||
_indexForEventId: function(evId) {
|
||||
|
@ -792,7 +843,7 @@ var TimelinePanel = React.createClass({
|
|||
return this.props.room.getEventReadUpTo(myUserId, ignoreSynthesized);
|
||||
},
|
||||
|
||||
_setReadMarker: function(eventId, eventTs) {
|
||||
_setReadMarker: function(eventId, eventTs, inhibitSetState) {
|
||||
if (TimelinePanel.roomReadMarkerMap[this.props.room.roomId] == eventId) {
|
||||
// don't update the state (and cause a re-render) if there is
|
||||
// no change to the RM.
|
||||
|
@ -807,6 +858,10 @@ var TimelinePanel = React.createClass({
|
|||
// above or below the visible timeline, we stash the timestamp.
|
||||
TimelinePanel.roomReadMarkerTsMap[this.props.room.roomId] = eventTs;
|
||||
|
||||
if (inhibitSetState) {
|
||||
return;
|
||||
}
|
||||
|
||||
// run the render cycle before calling the callback, so that
|
||||
// getReadMarkerPosition() returns the right thing.
|
||||
this.setState({
|
||||
|
@ -861,6 +916,7 @@ var TimelinePanel = React.createClass({
|
|||
stickyBottom={ stickyBottom }
|
||||
onScroll={ this.onMessageListScroll }
|
||||
onFillRequest={ this.onMessageListFillRequest }
|
||||
opacity={ this.props.opacity }
|
||||
/>
|
||||
);
|
||||
},
|
||||
|
|
|
@ -45,6 +45,17 @@ module.exports = React.createClass({displayName: 'UploadBar',
|
|||
|
||||
render: function() {
|
||||
var uploads = ContentMessages.getCurrentUploads();
|
||||
|
||||
// for testing UI... - also fix up the ContentMessages.getCurrentUploads().length
|
||||
// check in RoomView
|
||||
//
|
||||
// uploads = [{
|
||||
// roomId: this.props.room.roomId,
|
||||
// loaded: 123493,
|
||||
// total: 347534,
|
||||
// fileName: "testing_fooble.jpg",
|
||||
// }];
|
||||
|
||||
if (uploads.length == 0) {
|
||||
return <div />
|
||||
}
|
||||
|
|
|
@ -51,7 +51,11 @@ module.exports = React.createClass({
|
|||
},
|
||||
|
||||
componentWillMount: function() {
|
||||
var self = this;
|
||||
dis.dispatch({
|
||||
action: 'ui_opacity',
|
||||
sideOpacity: 0.3,
|
||||
middleOpacity: 0.3,
|
||||
});
|
||||
this._refreshFromServer();
|
||||
},
|
||||
|
||||
|
@ -61,6 +65,11 @@ module.exports = React.createClass({
|
|||
},
|
||||
|
||||
componentWillUnmount: function() {
|
||||
dis.dispatch({
|
||||
action: 'ui_opacity',
|
||||
sideOpacity: 1.0,
|
||||
middleOpacity: 1.0,
|
||||
});
|
||||
dis.unregister(this.dispatcherRef);
|
||||
},
|
||||
|
||||
|
@ -321,7 +330,7 @@ module.exports = React.createClass({
|
|||
var notification_area;
|
||||
if (!MatrixClientPeg.get().isGuest() && this.state.threepids !== undefined) {
|
||||
notification_area = (<div>
|
||||
<h2>Notifications</h2>
|
||||
<h3>Notifications</h3>
|
||||
|
||||
<div className="mx_UserSettings_section">
|
||||
<Notifications threepids={this.state.threepids} />
|
||||
|
@ -331,11 +340,13 @@ module.exports = React.createClass({
|
|||
|
||||
return (
|
||||
<div className="mx_UserSettings">
|
||||
<SimpleRoomHeader title="Settings"/>
|
||||
<SimpleRoomHeader title="Settings" onCancelClick={ this.props.onClose }/>
|
||||
|
||||
<GeminiScrollbar className="mx_UserSettings_body" autoshow={true}>
|
||||
<GeminiScrollbar className="mx_UserSettings_body"
|
||||
relayoutOnUpdate={false}
|
||||
autoshow={true}>
|
||||
|
||||
<h2>Profile</h2>
|
||||
<h3>Profile</h3>
|
||||
|
||||
<div className="mx_UserSettings_section">
|
||||
<div className="mx_UserSettings_profileTable">
|
||||
|
@ -366,10 +377,10 @@ module.exports = React.createClass({
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<h2>Account</h2>
|
||||
<h3>Account</h3>
|
||||
|
||||
<div className="mx_UserSettings_section">
|
||||
|
||||
|
||||
<div className="mx_UserSettings_logout mx_UserSettings_button" onClick={this.onLogoutClicked}>
|
||||
Log out
|
||||
</div>
|
||||
|
@ -379,7 +390,7 @@ module.exports = React.createClass({
|
|||
|
||||
{notification_area}
|
||||
|
||||
<h2>Advanced</h2>
|
||||
<h3>Advanced</h3>
|
||||
|
||||
<div className="mx_UserSettings_section">
|
||||
<div className="mx_UserSettings_advanced">
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue