Merge branch 'develop' into rte-fixes2

Conflicts:
	package.json
	src/autocomplete/CommandProvider.js
	src/autocomplete/UserProvider.js
	src/components/structures/RoomView.js
	src/components/structures/UserSettings.js
	src/components/views/rooms/MessageComposerInput.js
This commit is contained in:
Luke Barnard 2017-06-23 15:30:06 +01:00
commit 87609582c6
216 changed files with 21940 additions and 3614 deletions

View file

@ -25,6 +25,7 @@ var ReactDOM = require("react-dom");
var q = require("q");
var classNames = require("classnames");
var Matrix = require("matrix-js-sdk");
import { _t } from '../../languageHandler';
var UserSettingsStore = require('../../UserSettingsStore');
var MatrixClientPeg = require("../../MatrixClientPeg");
@ -44,6 +45,8 @@ import KeyCode from '../../KeyCode';
import UserProvider from '../../autocomplete/UserProvider';
import RoomViewStore from '../../stores/RoomViewStore';
var DEBUG = false;
if (DEBUG) {
@ -58,16 +61,9 @@ module.exports = React.createClass({
propTypes: {
ConferenceHandler: React.PropTypes.any,
// Either a room ID or room alias for the room to display.
// If the room is being displayed as a result of the user clicking
// on a room alias, the alias should be supplied. Otherwise, a room
// ID should be supplied.
roomAddress: React.PropTypes.string.isRequired,
// If a room alias is passed to roomAddress, a function can be
// provided here that will be called with the ID of the room
// once it has been resolved.
onRoomIdResolved: React.PropTypes.func,
// Called with the credentials of a registered user (if they were a ROU that
// transitioned to PWLU)
onRegistered: React.PropTypes.func,
// An object representing a third party invite to join this room
// Fields:
@ -87,36 +83,8 @@ module.exports = React.createClass({
// * invited us tovthe room
oobData: React.PropTypes.object,
// id of an event to jump to. If not given, will go to the end of the
// live timeline.
eventId: React.PropTypes.string,
// where to position the event given by eventId, in pixels from the
// bottom of the viewport. If not given, will try to put the event
// 1/3 of the way down the viewport.
eventPixelOffset: React.PropTypes.number,
// 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?
collapsedRhs: React.PropTypes.bool,
// a map from room id to scroll state, which will be updated on unmount.
//
// If there is no special scroll state (ie, we are following the live
// timeline), the scroll state is null. Otherwise, it is an object with
// the following properties:
//
// focussedEvent: the ID of the 'focussed' event. Typically this is
// the last event fully visible in the viewport, though if we
// have done an explicit scroll to an explicit event, it will be
// that event.
//
// pixelOffset: the number of pixels the window is scrolled down
// from the focussedEvent.
scrollStateMap: React.PropTypes.object,
},
getInitialState: function() {
@ -124,6 +92,17 @@ module.exports = React.createClass({
room: null,
roomId: null,
roomLoading: true,
peekLoading: false,
shouldPeek: true,
// The event to be scrolled to initially
initialEventId: null,
// The offset in pixels from the event with which to scroll vertically
initialEventPixelOffset: null,
// Whether to highlight the event scrolled to
isInitialEventHighlighted: null,
forwardingEvent: null,
editingRoomSettings: false,
uploadingRoomSettings: false,
numUnreadMessages: 0,
@ -169,40 +148,72 @@ module.exports = React.createClass({
onClickCompletes: true,
onStateChange: (isCompleting) => {
this.forceUpdate();
}
},
});
if (this.props.roomAddress[0] == '#') {
// we always look up the alias from the directory server:
// we want the room that the given alias is pointing to
// right now. We may have joined that alias before but there's
// no guarantee the alias hasn't subsequently been remapped.
MatrixClientPeg.get().getRoomIdForAlias(this.props.roomAddress).done((result) => {
if (this.props.onRoomIdResolved) {
this.props.onRoomIdResolved(result.room_id);
}
var room = MatrixClientPeg.get().getRoom(result.room_id);
this.setState({
room: room,
roomId: result.room_id,
roomLoading: !room,
unsentMessageError: this._getUnsentMessageError(room),
}, this._onHaveRoom);
}, (err) => {
this.setState({
roomLoading: false,
roomLoadError: err,
});
});
} else {
var room = MatrixClientPeg.get().getRoom(this.props.roomAddress);
this.setState({
roomId: this.props.roomAddress,
room: room,
roomLoading: !room,
unsentMessageError: this._getUnsentMessageError(room),
}, this._onHaveRoom);
// Start listening for RoomViewStore updates
this._roomStoreToken = RoomViewStore.addListener(this._onRoomViewStoreUpdate);
this._onRoomViewStoreUpdate(true);
},
_onRoomViewStoreUpdate: function(initial) {
if (this.unmounted) {
return;
}
const newState = {
roomId: RoomViewStore.getRoomId(),
roomAlias: RoomViewStore.getRoomAlias(),
roomLoading: RoomViewStore.isRoomLoading(),
roomLoadError: RoomViewStore.getRoomLoadError(),
joining: RoomViewStore.isJoining(),
initialEventId: RoomViewStore.getInitialEventId(),
initialEventPixelOffset: RoomViewStore.getInitialEventPixelOffset(),
isInitialEventHighlighted: RoomViewStore.isInitialEventHighlighted(),
forwardingEvent: RoomViewStore.getForwardingEvent(),
shouldPeek: RoomViewStore.shouldPeek(),
};
// finished joining, start waiting for a room and show a spinner. See onRoom.
newState.waitingForRoom = this.state.joining && !newState.joining &&
!RoomViewStore.getJoinError();
// Temporary logging to diagnose https://github.com/vector-im/riot-web/issues/4307
console.log(
'RVS update:',
newState.roomId,
newState.roomAlias,
'loading?', newState.roomLoading,
'joining?', newState.joining,
'initial?', initial,
'waiting?', newState.waitingForRoom,
'shouldPeek?', newState.shouldPeek,
);
// NB: This does assume that the roomID will not change for the lifetime of
// the RoomView instance
if (initial) {
newState.room = MatrixClientPeg.get().getRoom(newState.roomId);
}
// Clear the search results when clicking a search result (which changes the
// currently scrolled to event, this.state.initialEventId).
if (this.state.initialEventId !== newState.initialEventId) {
newState.searchResults = null;
}
// Store the scroll state for the previous room so that we can return to this
// position when viewing this room in future.
if (this.state.roomId !== newState.roomId) {
this._updateScrollMap(this.state.roomId);
}
this.setState(newState, () => {
// At this point, this.state.roomId could be null (e.g. the alias might not
// have been resolved yet) so anything called here must handle this case.
if (initial) {
this._onHaveRoom();
}
});
},
_onHaveRoom: function() {
@ -217,29 +228,31 @@ module.exports = React.createClass({
// which must be by alias or invite wherever possible (peeking currently does
// not work over federation).
// NB. We peek if we are not in the room, although if we try to peek into
// a room in which we have a member event (ie. we've left) synapse will just
// send us the same data as we get in the sync (ie. the last events we saw).
var user_is_in_room = null;
if (this.state.room) {
user_is_in_room = this.state.room.hasMembershipState(
MatrixClientPeg.get().credentials.userId, 'join'
);
UserProvider.getInstance().setUserListFromRoom(this.state.room);
this.tabComplete.loadEntries(this.state.room);
// NB. We peek if we have never seen the room before (i.e. js-sdk does not know
// about it). We don't peek in the historical case where we were joined but are
// now not joined because the js-sdk peeking API will clobber our historical room,
// making it impossible to indicate a newly joined room.
const room = this.state.room;
if (room) {
UserProvider.getInstance().setUserListFromRoom(room);
this.tabComplete.loadEntries(room);
this.setState({
unsentMessageError: this._getUnsentMessageError(room),
});
this._onRoomLoaded(room);
}
if (!user_is_in_room && this.state.roomId) {
if (!this.state.joining && this.state.roomId) {
if (this.props.autoJoin) {
this.onJoinButtonClicked();
} else if (this.state.roomId) {
} else if (!room && this.state.shouldPeek) {
console.log("Attempting to peek into room %s", this.state.roomId);
this.setState({
peekLoading: true,
});
MatrixClientPeg.get().peekInRoom(this.state.roomId).then((room) => {
this.setState({
room: room,
roomLoading: false,
peekLoading: false,
});
this._onRoomLoaded(room);
}, (err) => {
@ -249,16 +262,16 @@ module.exports = React.createClass({
if (err.errcode == "M_GUEST_ACCESS_FORBIDDEN") {
// This is fine: the room just isn't peekable (we assume).
this.setState({
roomLoading: false,
peekLoading: false,
});
} else {
throw err;
}
}).done();
}
} else if (user_is_in_room) {
} else if (room) {
// Stop peeking because we have joined this room previously
MatrixClientPeg.get().stopPeeking();
this._onRoomLoaded(this.state.room);
}
},
@ -294,17 +307,6 @@ module.exports = React.createClass({
}
},
componentWillReceiveProps: function(newProps) {
if (newProps.roomAddress != this.props.roomAddress) {
throw new Error("changing room on a RoomView is not supported");
}
if (newProps.eventId != this.props.eventId) {
// when we change focussed event id, hide the search results.
this.setState({searchResults: null});
}
},
shouldComponentUpdate: function(nextProps, nextState) {
return (!ObjectUtils.shallowEqual(this.props, nextProps) ||
!ObjectUtils.shallowEqual(this.state, nextState));
@ -330,7 +332,7 @@ module.exports = React.createClass({
this.unmounted = true;
// update the scroll map before we get unmounted
this._updateScrollMap();
this._updateScrollMap(this.state.roomId);
if (this.refs.roomView) {
// disconnect the D&D event listeners from the room view. This
@ -359,6 +361,11 @@ module.exports = React.createClass({
document.removeEventListener("keydown", this.onKeyDown);
// Remove RoomStore listener
if (this._roomStoreToken) {
this._roomStoreToken.remove();
}
// cancel any pending calls to the rate_limited_funcs
this._updateRoomMembers.cancelPendingCall();
@ -370,10 +377,10 @@ module.exports = React.createClass({
onPageUnload(event) {
if (ContentMessages.getCurrentUploads().length > 0) {
return event.returnValue =
'You seem to be uploading files, are you sure you want to quit?';
_t("You seem to be uploading files, are you sure you want to quit?");
} else if (this._getCallForRoom() && this.state.callState !== 'ended') {
return event.returnValue =
'You seem to be in a call, are you sure you want to quit?';
_t("You seem to be in a call, are you sure you want to quit?");
}
},
@ -518,7 +525,7 @@ module.exports = React.createClass({
this._updatePreviewUrlVisibility(room);
},
_warnAboutEncryption: function (room) {
_warnAboutEncryption: function(room) {
if (!MatrixClientPeg.get().isRoomEncrypted(room.roomId)) {
return;
}
@ -529,14 +536,14 @@ module.exports = React.createClass({
if (!userHasUsedEncryption) {
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
Modal.createDialog(QuestionDialog, {
title: "Warning!",
title: _t("Warning!"),
hasCancelButton: false,
description: (
<div>
<p>End-to-end encryption is in beta and may not be reliable.</p>
<p>You should <b>not</b> yet trust it to secure data.</p>
<p>Devices will <b>not</b> yet be able to decrypt history from before they joined the room.</p>
<p>Encrypted messages will not be visible on clients that do not yet implement encryption.</p>
<p>{ _t("End-to-end encryption is in beta and may not be reliable") }.</p>
<p>{ _t("You should not yet trust it to secure data") }.</p>
<p>{ _t("Devices will not yet be able to decrypt history from before they joined the room") }.</p>
<p>{ _t("Encrypted messages will not be visible on clients that do not yet implement encryption") }.</p>
</div>
),
});
@ -598,21 +605,28 @@ module.exports = React.createClass({
});
},
onRoom: function(room) {
// This event is fired when the room is 'stored' by the JS SDK, which
// means it's now a fully-fledged room object ready to be used, so
// 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 (!this.state.room && room.roomId == this._joiningRoomId) {
this._joiningRoomId = undefined;
this.setState({
room: room,
joining: false,
});
this._onRoomLoaded(room);
_updateScrollMap(roomId) {
// No point updating scroll state if the room ID hasn't been resolved yet
if (!roomId) {
return;
}
dis.dispatch({
action: 'update_scroll_state',
room_id: roomId,
scroll_state: this._getScrollState(),
});
},
onRoom: function(room) {
if (!room || room.roomId !== this.state.roomId) {
return;
}
this.setState({
room: room,
waitingForRoom: false,
}, () => {
this._onRoomLoaded(room);
});
},
updateTint: function() {
@ -665,7 +679,14 @@ module.exports = React.createClass({
onRoomMemberMembership: function(ev, member, oldMembership) {
if (member.userId == MatrixClientPeg.get().credentials.userId) {
this.forceUpdate();
if (member.membership === 'join') {
this.setState({
waitingForRoom: false,
});
} else {
this.forceUpdate();
}
}
},
@ -695,10 +716,6 @@ module.exports = React.createClass({
// compatability workaround, let's not bother.
Rooms.setDMRoom(this.state.room.roomId, me.events.member.getSender()).done();
}
this.setState({
joining: false
});
}
}, 500),
@ -707,10 +724,10 @@ module.exports = React.createClass({
if (!unsentMessages.length) return "";
for (const event of unsentMessages) {
if (!event.error || event.error.name !== "UnknownDeviceError") {
return "Some of your messages have not been sent.";
return _t("Some of your messages have not been sent.");
}
}
return "Message not sent due to unknown devices being present";
return _t("Message not sent due to unknown devices being present");
},
_getUnsentMessages: function(room) {
@ -773,41 +790,62 @@ module.exports = React.createClass({
},
onJoinButtonClicked: function(ev) {
var self = this;
const cli = MatrixClientPeg.get();
var cli = MatrixClientPeg.get();
var display_name_promise = q();
// if this is the first room we're joining, check the user has a display name
// and if they don't, prompt them to set one.
// NB. This unfortunately does not re-use the ChangeDisplayName component because
// it doesn't behave quite as desired here (we want an input field here rather than
// content-editable, and we want a default).
if (cli.getRooms().filter((r) => {
return r.hasMembershipState(cli.credentials.userId, "join");
})) {
display_name_promise = cli.getProfileInfo(cli.credentials.userId).then((result) => {
if (!result.displayname) {
var SetDisplayNameDialog = sdk.getComponent('views.dialogs.SetDisplayNameDialog');
var dialog_defer = q.defer();
Modal.createDialog(SetDisplayNameDialog, {
currentDisplayName: result.displayname,
onFinished: (submitted, newDisplayName) => {
if (submitted) {
cli.setDisplayName(newDisplayName).done(() => {
dialog_defer.resolve();
});
}
else {
dialog_defer.reject();
}
}
});
return dialog_defer.promise;
}
// If the user is a ROU, allow them to transition to a PWLU
if (cli && cli.isGuest()) {
// Join this room once the user has registered and logged in
const signUrl = this.props.thirdPartyInvite ?
this.props.thirdPartyInvite.inviteSignUrl : undefined;
dis.dispatch({
action: 'do_after_sync_prepared',
deferred_action: {
action: 'join_room',
opts: { inviteSignUrl: signUrl },
},
});
// Don't peek whilst registering otherwise getPendingEventList complains
// Do this by indicating our intention to join
dis.dispatch({
action: 'will_join',
});
const SetMxIdDialog = sdk.getComponent('views.dialogs.SetMxIdDialog');
const close = Modal.createDialog(SetMxIdDialog, {
homeserverUrl: cli.getHomeserverUrl(),
onFinished: (submitted, credentials) => {
if (submitted) {
this.props.onRegistered(credentials);
} else {
dis.dispatch({
action: 'cancel_after_sync_prepared',
});
dis.dispatch({
action: 'cancel_join',
});
}
},
onDifferentServerClicked: (ev) => {
dis.dispatch({action: 'start_registration'});
close();
},
onLoginClick: (ev) => {
dis.dispatch({action: 'start_login'});
close();
},
}).close;
return;
}
display_name_promise.then(() => {
q().then(() => {
const signUrl = this.props.thirdPartyInvite ?
this.props.thirdPartyInvite.inviteSignUrl : undefined;
dis.dispatch({
action: 'join_room',
opts: { inviteSignUrl: signUrl },
});
// if this is an invite and has the 'direct' hint set, mark it as a DM room now.
if (this.state.room) {
const me = this.state.room.getMember(MatrixClientPeg.get().credentials.userId);
@ -819,72 +857,7 @@ module.exports = React.createClass({
}
}
}
return q();
}).then(() => {
var sign_url = this.props.thirdPartyInvite ? this.props.thirdPartyInvite.inviteSignUrl : undefined;
return MatrixClientPeg.get().joinRoom(this.props.roomAddress,
{ inviteSignUrl: sign_url } );
}).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.
// 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(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
});
if (!error) return;
// https://matrix.org/jira/browse/SYN-659
// Need specific error message if joining a room is refused because the user is a guest and guest access is not allowed
if (
error.errcode == 'M_GUEST_ACCESS_FORBIDDEN' ||
(
error.errcode == 'M_FORBIDDEN' &&
MatrixClientPeg.get().isGuest()
)
) {
var NeedToRegisterDialog = sdk.getComponent("dialogs.NeedToRegisterDialog");
Modal.createDialog(NeedToRegisterDialog, {
title: "Failed to join the room",
description: "This room is private or inaccessible to guests. You may be able to join if you register."
});
} else {
var msg = error.message ? error.message : JSON.stringify(error);
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createDialog(ErrorDialog, {
title: "Failed to join room",
description: msg
});
}
}).done();
this.setState({
joining: true
});
},
@ -936,11 +909,7 @@ module.exports = React.createClass({
uploadFile: function(file) {
if (MatrixClientPeg.get().isGuest()) {
var NeedToRegisterDialog = sdk.getComponent("dialogs.NeedToRegisterDialog");
Modal.createDialog(NeedToRegisterDialog, {
title: "Please Register",
description: "Guest users can't upload files. Please register to upload."
});
dis.dispatch({action: 'view_set_mxid'});
return;
}
@ -958,8 +927,8 @@ module.exports = React.createClass({
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
console.error("Failed to upload file " + file + " " + error);
Modal.createDialog(ErrorDialog, {
title: "Failed to upload file",
description: ((error && error.message) ? error.message : "Server may be unavailable, overloaded, or the file too big"),
title: _t('Failed to upload file'),
description: ((error && error.message) ? error.message : _t("Server may be unavailable, overloaded, or the file too big")),
});
});
},
@ -1045,8 +1014,8 @@ module.exports = React.createClass({
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
console.error("Search failed: " + error);
Modal.createDialog(ErrorDialog, {
title: "Search failed",
description: ((error && error.message) ? error.message : "Server may be unavailable, overloaded, or search timed out :("),
title: _t("Search failed"),
description: ((error && error.message) ? error.message : _t("Server may be unavailable, overloaded, or search timed out :(")),
});
}).finally(function() {
self.setState({
@ -1081,12 +1050,12 @@ module.exports = React.createClass({
if (!this.state.searchResults.next_batch) {
if (this.state.searchResults.results.length == 0) {
ret.push(<li key="search-top-marker">
<h2 className="mx_RoomView_topMarker">No results</h2>
<h2 className="mx_RoomView_topMarker">{ _t("No results") }</h2>
</li>
);
} else {
ret.push(<li key="search-top-marker">
<h2 className="mx_RoomView_topMarker">No more results</h2>
<h2 className="mx_RoomView_topMarker">{ _t("No more results") }</h2>
</li>
);
}
@ -1123,10 +1092,10 @@ module.exports = React.createClass({
// it. We should tell the js sdk to go and find out about
// it. But that's not an issue currently, as synapse only
// returns results for rooms we're joined to.
var roomName = room ? room.name : "Unknown room "+roomId;
var roomName = room ? room.name : _t("Unknown room %(roomId)s", { roomId: roomId });
ret.push(<li key={mxEv.getId() + "-room"}>
<h1>Room: { roomName }</h1>
<h1>{ _t("Room") }: { roomName }</h1>
</li>);
lastRoomId = roomId;
}
@ -1172,7 +1141,7 @@ module.exports = React.createClass({
});
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createDialog(ErrorDialog, {
title: "Failed to save settings",
title: _t("Failed to save settings"),
description: fails.map(function(result) { return result.reason; }).join("\n"),
});
// still editing room settings
@ -1193,7 +1162,15 @@ module.exports = React.createClass({
onCancelClick: function() {
console.log("updateTint from onCancelClick");
this.updateTint();
this.setState({editingRoomSettings: false});
this.setState({
editingRoomSettings: false,
});
if (this.state.forwardingEvent) {
dis.dispatch({
action: 'forward_event',
event: null,
});
}
dis.dispatch({action: 'focus_composer'});
},
@ -1208,11 +1185,11 @@ module.exports = React.createClass({
MatrixClientPeg.get().forget(this.state.room.roomId).done(function() {
dis.dispatch({ action: 'view_next_room' });
}, function(err) {
var errCode = err.errcode || "unknown error code";
var errCode = err.errcode || _t("unknown error code");
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createDialog(ErrorDialog, {
title: "Error",
description: `Failed to forget room (${errCode})`
title: _t("Error"),
description: _t("Failed to forget room %(errCode)s", { errCode: errCode }),
});
});
},
@ -1233,8 +1210,8 @@ module.exports = React.createClass({
var msg = error.message ? error.message : JSON.stringify(error);
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createDialog(ErrorDialog, {
title: "Failed to reject invite",
description: msg
title: _t("Failed to reject invite"),
description: msg,
});
self.setState({
@ -1295,21 +1272,6 @@ module.exports = React.createClass({
}
},
// update scrollStateMap on unmount
_updateScrollMap: function() {
if (!this.state.room) {
// we were instantiated on a room alias and haven't yet joined the room.
return;
}
if (!this.props.scrollStateMap) return;
var roomId = this.state.room.roomId;
var state = this._getScrollState();
this.props.scrollStateMap[roomId] = state;
},
// get the current scroll position of the room, so that it can be
// restored when we switch back to it.
//
@ -1463,61 +1425,65 @@ module.exports = React.createClass({
},
render: function() {
var RoomHeader = sdk.getComponent('rooms.RoomHeader');
var MessageComposer = sdk.getComponent('rooms.MessageComposer');
var RoomSettings = sdk.getComponent("rooms.RoomSettings");
var AuxPanel = sdk.getComponent("rooms.AuxPanel");
var SearchBar = sdk.getComponent("rooms.SearchBar");
var ScrollPanel = sdk.getComponent("structures.ScrollPanel");
var TintableSvg = sdk.getComponent("elements.TintableSvg");
var RoomPreviewBar = sdk.getComponent("rooms.RoomPreviewBar");
var Loader = sdk.getComponent("elements.Spinner");
var TimelinePanel = sdk.getComponent("structures.TimelinePanel");
const RoomHeader = sdk.getComponent('rooms.RoomHeader');
const MessageComposer = sdk.getComponent('rooms.MessageComposer');
const ForwardMessage = sdk.getComponent("rooms.ForwardMessage");
const RoomSettings = sdk.getComponent("rooms.RoomSettings");
const AuxPanel = sdk.getComponent("rooms.AuxPanel");
const SearchBar = sdk.getComponent("rooms.SearchBar");
const ScrollPanel = sdk.getComponent("structures.ScrollPanel");
const TintableSvg = sdk.getComponent("elements.TintableSvg");
const RoomPreviewBar = sdk.getComponent("rooms.RoomPreviewBar");
const Loader = sdk.getComponent("elements.Spinner");
const TimelinePanel = sdk.getComponent("structures.TimelinePanel");
// Whether the preview bar spinner should be shown. We do this when joining or
// when waiting for a room to be returned by js-sdk when joining
const previewBarSpinner = this.state.joining || this.state.waitingForRoom;
if (!this.state.room) {
if (this.state.roomLoading) {
return (
<div className="mx_RoomView">
<Loader />
</div>
);
if (this.state.roomLoading || this.state.peekLoading) {
return (
<div className="mx_RoomView">
<Loader />
</div>
);
} else {
var inviterName = undefined;
if (this.props.oobData) {
inviterName = this.props.oobData.inviterName;
}
var invitedEmail = undefined;
if (this.props.thirdPartyInvite) {
invitedEmail = this.props.thirdPartyInvite.invitedEmail;
}
else {
var inviterName = undefined;
if (this.props.oobData) {
inviterName = this.props.oobData.inviterName;
}
var invitedEmail = undefined;
if (this.props.thirdPartyInvite) {
invitedEmail = this.props.thirdPartyInvite.invitedEmail;
}
// We have no room object for this room, only the ID.
// We've got to this room by following a link, possibly a third party invite.
var room_alias = this.props.roomAddress[0] == '#' ? this.props.roomAddress : null;
return (
<div className="mx_RoomView">
<RoomHeader ref="header"
room={this.state.room}
oobData={this.props.oobData}
collapsedRhs={ this.props.collapsedRhs }
// We have no room object for this room, only the ID.
// We've got to this room by following a link, possibly a third party invite.
const roomAlias = this.state.roomAlias;
return (
<div className="mx_RoomView">
<RoomHeader ref="header"
room={this.state.room}
oobData={this.props.oobData}
collapsedRhs={ this.props.collapsedRhs }
/>
<div className="mx_RoomView_auxPanel">
<RoomPreviewBar onJoinClick={ this.onJoinButtonClicked }
onForgetClick={ this.onForgetClick }
onRejectClick={ this.onRejectThreepidInviteButtonClicked }
canPreview={ false } error={ this.state.roomLoadError }
roomAlias={roomAlias}
spinner={previewBarSpinner}
inviterName={inviterName}
invitedEmail={invitedEmail}
room={this.state.room}
/>
<div className="mx_RoomView_auxPanel">
<RoomPreviewBar onJoinClick={ this.onJoinButtonClicked }
onForgetClick={ this.onForgetClick }
onRejectClick={ this.onRejectThreepidInviteButtonClicked }
canPreview={ false } error={ this.state.roomLoadError }
roomAlias={room_alias}
spinner={this.state.joining}
inviterName={inviterName}
invitedEmail={invitedEmail}
room={this.state.room}
/>
</div>
<div className="mx_RoomView_messagePanel"></div>
</div>
);
}
<div className="mx_RoomView_messagePanel"></div>
</div>
);
}
}
var myUserId = MatrixClientPeg.get().credentials.userId;
@ -1551,7 +1517,7 @@ module.exports = React.createClass({
onRejectClick={ this.onRejectButtonClicked }
inviterName={ inviterName }
canPreview={ false }
spinner={this.state.joining}
spinner={previewBarSpinner}
room={this.state.room}
/>
</div>
@ -1600,17 +1566,18 @@ module.exports = React.createClass({
/>;
}
var aux = null;
let aux = null;
let hideCancel = false;
if (this.state.editingRoomSettings) {
aux = <RoomSettings ref="room_settings" onSaveClick={this.onSettingsSaveClick} onCancelClick={this.onCancelClick} room={this.state.room} />;
}
else if (this.state.uploadingRoomSettings) {
} else if (this.state.uploadingRoomSettings) {
aux = <Loader/>;
}
else if (this.state.searching) {
} else if (this.state.forwardingEvent !== null) {
aux = <ForwardMessage onCancelClick={this.onCancelClick} />;
} else if (this.state.searching) {
hideCancel = true; // has own cancel
aux = <SearchBar ref="search_bar" searchInProgress={this.state.searchInProgress } onCancelClick={this.onCancelSearchClick} onSearch={this.onSearch}/>;
}
else if (!myMember || myMember.membership !== "join") {
} else if (!myMember || myMember.membership !== "join") {
// We do have a room object for this room, but we're not currently in it.
// We may have a 3rd party invite to it.
var inviterName = undefined;
@ -1621,11 +1588,12 @@ module.exports = React.createClass({
if (this.props.thirdPartyInvite) {
invitedEmail = this.props.thirdPartyInvite.invitedEmail;
}
hideCancel = true;
aux = (
<RoomPreviewBar onJoinClick={this.onJoinButtonClicked}
onForgetClick={ this.onForgetClick }
onRejectClick={this.onRejectThreepidInviteButtonClicked}
spinner={this.state.joining}
spinner={previewBarSpinner}
inviterName={inviterName}
invitedEmail={invitedEmail}
canPreview={this.state.canPeek}
@ -1672,7 +1640,7 @@ module.exports = React.createClass({
if (call.type === "video") {
zoomButton = (
<div className="mx_RoomView_voipButton" onClick={this.onFullscreenClick} title="Fill screen">
<div className="mx_RoomView_voipButton" onClick={this.onFullscreenClick} title={ _t("Fill screen") }>
<TintableSvg src="img/fullscreen.svg" width="29" height="22" style={{ marginTop: 1, marginRight: 4 }}/>
</div>
);
@ -1680,14 +1648,14 @@ module.exports = React.createClass({
videoMuteButton =
<div className="mx_RoomView_voipButton" onClick={this.onMuteVideoClick}>
<TintableSvg src={call.isLocalVideoMuted() ? "img/video-unmute.svg" : "img/video-mute.svg"}
alt={call.isLocalVideoMuted() ? "Click to unmute video" : "Click to mute video"}
alt={call.isLocalVideoMuted() ? _t("Click to unmute video") : _t("Click to mute video")}
width="31" height="27"/>
</div>;
}
voiceMuteButton =
<div className="mx_RoomView_voipButton" onClick={this.onMuteAudioClick}>
<TintableSvg src={call.isMicrophoneMuted() ? "img/voice-unmute.svg" : "img/voice-mute.svg"}
alt={call.isMicrophoneMuted() ? "Click to unmute audio" : "Click to mute audio"}
alt={call.isMicrophoneMuted() ? _t("Click to unmute audio") : _t("Click to mute audio")}
width="21" height="26"/>
</div>;
@ -1722,17 +1690,24 @@ module.exports = React.createClass({
hideMessagePanel = true;
}
// console.log("ShowUrlPreview for %s is %s", this.state.room.roomId, this.state.showUrlPreview);
const shouldHighlight = this.state.isInitialEventHighlighted;
let highlightedEventId = null;
if (this.state.forwardingEvent) {
highlightedEventId = this.state.forwardingEvent.getId();
} else if (shouldHighlight) {
highlightedEventId = this.state.initialEventId;
}
// console.log("ShowUrlPreview for %s is %s", this.state.room.roomId, this.state.showUrlPreview);
var messagePanel = (
<TimelinePanel ref={this._gatherTimelinePanelRef}
timelineSet={this.state.room.getUnfilteredTimelineSet()}
manageReadReceipts={!UserSettingsStore.getSyncedSetting('hideReadReceipts', false)}
manageReadMarkers={true}
hidden={hideMessagePanel}
highlightedEventId={this.props.highlightedEventId}
eventId={this.props.eventId}
eventPixelOffset={this.props.eventPixelOffset}
highlightedEventId={highlightedEventId}
eventId={this.state.initialEventId}
eventPixelOffset={this.state.initialEventPixelOffset}
onScroll={ this.onMessageListScroll }
onReadMarkerUpdated={ this._updateTopUnreadMessagesBar }
showUrlPreview = { this.state.showUrlPreview }
@ -1763,17 +1738,15 @@ module.exports = React.createClass({
oobData={this.props.oobData}
editing={this.state.editingRoomSettings}
saving={this.state.uploadingRoomSettings}
inRoom={myMember && myMember.membership === 'join'}
collapsedRhs={ this.props.collapsedRhs }
onSearchClick={this.onSearchClick}
onSettingsClick={this.onSettingsClick}
onSaveClick={this.onSettingsSaveClick}
onCancelClick={this.onCancelClick}
onForgetClick={
(myMember && myMember.membership === "leave") ? this.onForgetClick : null
}
onLeaveClick={
(myMember && myMember.membership === "join") ? this.onLeaveClick : null
} />
onCancelClick={(aux && !hideCancel) ? this.onCancelClick : null}
onForgetClick={(myMember && myMember.membership === "leave") ? this.onForgetClick : null}
onLeaveClick={(myMember && myMember.membership === "join") ? this.onLeaveClick : null}
/>
{ auxPanel }
{ topUnreadMessagesBar }
{ messagePanel }