merge in develop
This commit is contained in:
110
src/components/structures/ContextualMenu.js
Normal file
110
src/components/structures/ContextualMenu.js
Normal file
@@ -0,0 +1,110 @@
|
||||
/*
|
||||
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 classNames = require('classnames');
|
||||
var React = require('react');
|
||||
var ReactDOM = require('react-dom');
|
||||
|
||||
// Shamelessly ripped off Modal.js. There's probably a better way
|
||||
// of doing reusable widgets like dialog boxes & menus where we go and
|
||||
// pass in a custom control as the actual body.
|
||||
|
||||
module.exports = {
|
||||
ContextualMenuContainerId: "mx_ContextualMenu_Container",
|
||||
|
||||
propTypes: {
|
||||
menuWidth: React.PropTypes.number,
|
||||
menuHeight: React.PropTypes.number,
|
||||
chevronOffset: React.PropTypes.number,
|
||||
},
|
||||
|
||||
getOrCreateContainer: function() {
|
||||
var container = document.getElementById(this.ContextualMenuContainerId);
|
||||
|
||||
if (!container) {
|
||||
container = document.createElement("div");
|
||||
container.id = this.ContextualMenuContainerId;
|
||||
document.body.appendChild(container);
|
||||
}
|
||||
|
||||
return container;
|
||||
},
|
||||
|
||||
createMenu: function (Element, props) {
|
||||
var self = this;
|
||||
|
||||
var closeMenu = function() {
|
||||
ReactDOM.unmountComponentAtNode(self.getOrCreateContainer());
|
||||
|
||||
if (props && props.onFinished) {
|
||||
props.onFinished.apply(null, arguments);
|
||||
}
|
||||
};
|
||||
|
||||
var position = {
|
||||
top: props.top,
|
||||
};
|
||||
|
||||
var chevronOffset = {
|
||||
top: props.chevronOffset,
|
||||
}
|
||||
|
||||
var chevron = null;
|
||||
if (props.left) {
|
||||
chevron = <div style={chevronOffset} className="mx_ContextualMenu_chevron_left"></div>
|
||||
position.left = props.left;
|
||||
} else {
|
||||
chevron = <div style={chevronOffset} className="mx_ContextualMenu_chevron_right"></div>
|
||||
position.right = props.right;
|
||||
}
|
||||
|
||||
var className = 'mx_ContextualMenu_wrapper';
|
||||
|
||||
var menuClasses = classNames({
|
||||
'mx_ContextualMenu': true,
|
||||
'mx_ContextualMenu_left': props.left,
|
||||
'mx_ContextualMenu_right': !props.left,
|
||||
});
|
||||
|
||||
var menuSize = {};
|
||||
if (props.menuWidth) {
|
||||
menuSize.width = props.menuWidth;
|
||||
}
|
||||
|
||||
if (props.menuHeight) {
|
||||
menuSize.height = props.menuHeight;
|
||||
}
|
||||
|
||||
// FIXME: If a menu uses getDefaultProps it clobbers the onFinished
|
||||
// property set here so you can't close the menu from a button click!
|
||||
var menu = (
|
||||
<div className={className} style={position}>
|
||||
<div className={menuClasses} style={menuSize}>
|
||||
{chevron}
|
||||
<Element {...props} onFinished={closeMenu}/>
|
||||
</div>
|
||||
<div className="mx_ContextualMenu_background" onClick={closeMenu}></div>
|
||||
</div>
|
||||
);
|
||||
|
||||
ReactDOM.render(menu, this.getOrCreateContainer());
|
||||
|
||||
return {close: closeMenu};
|
||||
},
|
||||
};
|
||||
@@ -24,7 +24,6 @@ var PresetValues = {
|
||||
Custom: "custom",
|
||||
};
|
||||
var q = require('q');
|
||||
var encryption = require("../../encryption");
|
||||
var sdk = require('../../index');
|
||||
|
||||
module.exports = React.createClass({
|
||||
@@ -108,17 +107,8 @@ module.exports = React.createClass({
|
||||
|
||||
var deferred = cli.createRoom(options);
|
||||
|
||||
var response;
|
||||
|
||||
if (this.state.encrypt) {
|
||||
deferred = deferred.then(function(res) {
|
||||
response = res;
|
||||
return encryption.enableEncryption(
|
||||
cli, response.room_id, options.invite
|
||||
);
|
||||
}).then(function() {
|
||||
return q(response) }
|
||||
);
|
||||
// TODO
|
||||
}
|
||||
|
||||
this.setState({
|
||||
|
||||
@@ -21,7 +21,7 @@ var Favico = require('favico.js');
|
||||
var MatrixClientPeg = require("../../MatrixClientPeg");
|
||||
var SdkConfig = require("../../SdkConfig");
|
||||
var Notifier = require("../../Notifier");
|
||||
var ContextualMenu = require("../../ContextualMenu");
|
||||
var ContextualMenu = require("./ContextualMenu");
|
||||
var RoomListSorter = require("../../RoomListSorter");
|
||||
var UserActivity = require("../../UserActivity");
|
||||
var Presence = require("../../Presence");
|
||||
@@ -37,6 +37,7 @@ var sdk = require('../../index');
|
||||
var MatrixTools = require('../../MatrixTools');
|
||||
var linkifyMatrix = require("../../linkify-matrix");
|
||||
var KeyCode = require('../../KeyCode');
|
||||
var Lifecycle = require('../../Lifecycle');
|
||||
|
||||
var createRoom = require("../../createRoom");
|
||||
|
||||
@@ -109,10 +110,14 @@ module.exports = React.createClass({
|
||||
return window.localStorage.getItem("mx_hs_url");
|
||||
}
|
||||
else {
|
||||
return this.props.config.default_hs_url || "https://matrix.org";
|
||||
return this.getDefaultHsUrl();
|
||||
}
|
||||
},
|
||||
|
||||
getDefaultHsUrl() {
|
||||
return this.props.config.default_hs_url || "https://matrix.org";
|
||||
},
|
||||
|
||||
getFallbackHsUrl: function() {
|
||||
return this.props.config.fallback_hs_url;
|
||||
},
|
||||
@@ -127,16 +132,31 @@ module.exports = React.createClass({
|
||||
return window.localStorage.getItem("mx_is_url");
|
||||
}
|
||||
else {
|
||||
return this.props.config.default_is_url || "https://vector.im"
|
||||
return this.getDefaultIsUrl();
|
||||
}
|
||||
},
|
||||
|
||||
getDefaultIsUrl() {
|
||||
return this.props.config.default_is_url || "https://vector.im";
|
||||
},
|
||||
|
||||
componentWillMount: function() {
|
||||
SdkConfig.put(this.props.config);
|
||||
this.favicon = new Favico({animation: 'none'});
|
||||
|
||||
// Stashed guest credentials if the user logs out
|
||||
// whilst logged in as a guest user (so they can change
|
||||
// their mind & log back in)
|
||||
this.guestCreds = null;
|
||||
|
||||
if (this.props.config.sync_timeline_limit) {
|
||||
MatrixClientPeg.opts.initialSyncLimit = this.props.config.sync_timeline_limit;
|
||||
}
|
||||
},
|
||||
|
||||
componentDidMount: function() {
|
||||
let clientStarted = false;
|
||||
|
||||
this._autoRegisterAsGuest = false;
|
||||
if (this.props.enableGuest) {
|
||||
if (!this.getCurrentHsUrl()) {
|
||||
@@ -150,13 +170,14 @@ module.exports = React.createClass({
|
||||
this.props.startingQueryParams.guest_access_token)
|
||||
{
|
||||
this._autoRegisterAsGuest = false;
|
||||
this.onLoggedIn({
|
||||
Lifecycle.setLoggedIn({
|
||||
userId: this.props.startingQueryParams.guest_user_id,
|
||||
accessToken: this.props.startingQueryParams.guest_access_token,
|
||||
homeserverUrl: this.props.config.default_hs_url,
|
||||
identityServerUrl: this.props.config.default_is_url,
|
||||
homeserverUrl: this.getDefaultHsUrl(),
|
||||
identityServerUrl: this.getDefaultIsUrl(),
|
||||
guest: true
|
||||
});
|
||||
clientStarted = true;
|
||||
}
|
||||
else {
|
||||
this._autoRegisterAsGuest = true;
|
||||
@@ -168,7 +189,9 @@ module.exports = React.createClass({
|
||||
// Don't auto-register as a guest. This applies if you refresh the page on a
|
||||
// logged in client THEN hit the Sign Out button.
|
||||
this._autoRegisterAsGuest = false;
|
||||
this.startMatrixClient();
|
||||
if (!clientStarted) {
|
||||
Lifecycle.startMatrixClient();
|
||||
}
|
||||
}
|
||||
this.focusComposer = false;
|
||||
// scrollStateMap is a map from room id to the scroll state returned by
|
||||
@@ -223,7 +246,7 @@ module.exports = React.createClass({
|
||||
MatrixClientPeg.get().registerGuest().done(function(creds) {
|
||||
console.log("Registered as guest: %s", creds.user_id);
|
||||
self._setAutoRegisterAsGuest(false);
|
||||
self.onLoggedIn({
|
||||
Lifecycle.setLoggedIn({
|
||||
userId: creds.user_id,
|
||||
accessToken: creds.access_token,
|
||||
homeserverUrl: hsUrl,
|
||||
@@ -254,34 +277,10 @@ module.exports = React.createClass({
|
||||
var self = this;
|
||||
switch (payload.action) {
|
||||
case 'logout':
|
||||
var guestCreds;
|
||||
if (MatrixClientPeg.get().isGuest()) {
|
||||
guestCreds = { // stash our guest creds so we can backout if needed
|
||||
userId: MatrixClientPeg.get().credentials.userId,
|
||||
accessToken: MatrixClientPeg.get().getAccessToken(),
|
||||
homeserverUrl: MatrixClientPeg.get().getHomeserverUrl(),
|
||||
identityServerUrl: MatrixClientPeg.get().getIdentityServerUrl(),
|
||||
guest: true
|
||||
}
|
||||
this.guestCreds = MatrixClientPeg.getCredentials();
|
||||
}
|
||||
|
||||
if (window.localStorage) {
|
||||
var hsUrl = this.getCurrentHsUrl();
|
||||
var isUrl = this.getCurrentIsUrl();
|
||||
window.localStorage.clear();
|
||||
// preserve our HS & IS URLs for convenience
|
||||
// N.B. we cache them in hsUrl/isUrl and can't really inline them
|
||||
// as getCurrentHsUrl() may call through to localStorage.
|
||||
window.localStorage.setItem("mx_hs_url", hsUrl);
|
||||
window.localStorage.setItem("mx_is_url", isUrl);
|
||||
}
|
||||
this._stopMatrixClient();
|
||||
this.notifyNewScreen('login');
|
||||
this.replaceState({
|
||||
logged_in: false,
|
||||
ready: false,
|
||||
guestCreds: guestCreds,
|
||||
});
|
||||
Lifecycle.logout();
|
||||
break;
|
||||
case 'start_registration':
|
||||
var newState = payload.params || {};
|
||||
@@ -307,7 +306,6 @@ module.exports = React.createClass({
|
||||
if (this.state.logged_in) return;
|
||||
this.replaceState({
|
||||
screen: 'login',
|
||||
guestCreds: this.state.guestCreds,
|
||||
});
|
||||
this.notifyNewScreen('login');
|
||||
break;
|
||||
@@ -317,17 +315,12 @@ module.exports = React.createClass({
|
||||
});
|
||||
break;
|
||||
case 'start_upgrade_registration':
|
||||
// stash our guest creds so we can backout if needed
|
||||
this.guestCreds = MatrixClientPeg.getCredentials();
|
||||
this.replaceState({
|
||||
screen: "register",
|
||||
upgradeUsername: MatrixClientPeg.get().getUserIdLocalpart(),
|
||||
guestAccessToken: MatrixClientPeg.get().getAccessToken(),
|
||||
guestCreds: { // stash our guest creds so we can backout if needed
|
||||
userId: MatrixClientPeg.get().credentials.userId,
|
||||
accessToken: MatrixClientPeg.get().getAccessToken(),
|
||||
homeserverUrl: MatrixClientPeg.get().getHomeserverUrl(),
|
||||
identityServerUrl: MatrixClientPeg.get().getIdentityServerUrl(),
|
||||
guest: true
|
||||
}
|
||||
});
|
||||
this.notifyNewScreen('register');
|
||||
break;
|
||||
@@ -349,10 +342,13 @@ module.exports = React.createClass({
|
||||
|
||||
var client = MatrixClientPeg.get();
|
||||
client.loginWithToken(payload.params.loginToken).done(function(data) {
|
||||
MatrixClientPeg.replaceUsingAccessToken(
|
||||
client.getHomeserverUrl(), client.getIdentityServerUrl(),
|
||||
data.user_id, data.access_token
|
||||
);
|
||||
MatrixClientPeg.replaceUsingCreds({
|
||||
homeserverUrl: client.getHomeserverUrl(),
|
||||
identityServerUrl: client.getIdentityServerUrl(),
|
||||
userId: data.user_id,
|
||||
accessToken: data.access_token,
|
||||
guest: false,
|
||||
});
|
||||
self.setState({
|
||||
screen: undefined,
|
||||
logged_in: true
|
||||
@@ -384,7 +380,7 @@ module.exports = React.createClass({
|
||||
|
||||
// FIXME: controller shouldn't be loading a view :(
|
||||
var Loader = sdk.getComponent("elements.Spinner");
|
||||
var modal = Modal.createDialog(Loader);
|
||||
var modal = Modal.createDialog(Loader, null, 'mx_Dialog_spinner');
|
||||
|
||||
d.then(function() {
|
||||
modal.close();
|
||||
@@ -405,10 +401,7 @@ module.exports = React.createClass({
|
||||
// known to be in (eg. user clicks on a room in the recents panel), supply the ID
|
||||
// If the user is clicking on a room in the context of the alias being presented
|
||||
// to them, supply the room alias. If both are supplied, the room ID will be ignored.
|
||||
this._viewRoom(
|
||||
payload.room_id, payload.room_alias, payload.show_settings, payload.event_id,
|
||||
payload.third_party_invite, payload.oob_data
|
||||
);
|
||||
this._viewRoom(payload);
|
||||
break;
|
||||
case 'view_prev_room':
|
||||
roomIndexDelta = -1;
|
||||
@@ -425,7 +418,7 @@ module.exports = React.createClass({
|
||||
}
|
||||
roomIndex = (roomIndex + roomIndexDelta) % allRooms.length;
|
||||
if (roomIndex < 0) roomIndex = allRooms.length - 1;
|
||||
this._viewRoom(allRooms[roomIndex].roomId);
|
||||
this._viewRoom({ room_id: allRooms[roomIndex].roomId });
|
||||
break;
|
||||
case 'view_indexed_room':
|
||||
var allRooms = RoomListSorter.mostRecentActivityFirst(
|
||||
@@ -433,7 +426,7 @@ module.exports = React.createClass({
|
||||
);
|
||||
var roomIndex = payload.roomIndex;
|
||||
if (allRooms[roomIndex]) {
|
||||
this._viewRoom(allRooms[roomIndex].roomId);
|
||||
this._viewRoom({ room_id: allRooms[roomIndex].roomId });
|
||||
}
|
||||
break;
|
||||
case 'view_user_settings':
|
||||
@@ -479,6 +472,15 @@ module.exports = React.createClass({
|
||||
middleOpacity: payload.middleOpacity,
|
||||
});
|
||||
break;
|
||||
case 'on_logged_in':
|
||||
this._onLoggedIn();
|
||||
break;
|
||||
case 'on_logged_out':
|
||||
this._onLoggedOut();
|
||||
break;
|
||||
case 'will_start_client':
|
||||
this._onWillStartClient();
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
@@ -493,39 +495,45 @@ module.exports = React.createClass({
|
||||
|
||||
// switch view to the given room
|
||||
//
|
||||
// eventId is optional and will cause a switch to the context of that
|
||||
// particular event.
|
||||
// @param {Object} thirdPartyInvite Object containing data about the third party
|
||||
// @param {Object} room_info Object containing data about the room to be joined
|
||||
// @param {string=} room_info.room_id ID of the room to join. One of room_id or room_alias must be given.
|
||||
// @param {string=} room_info.room_alias Alias of the room to join. One of room_id or room_alias must be given.
|
||||
// @param {boolean=} room_info.auto_join If true, automatically attempt to join the room if not already a member.
|
||||
// @param {boolean=} room_info.show_settings Makes RoomView show the room settings dialog.
|
||||
// @param {string=} room_info.event_id ID of the event in this room to show: this will cause a switch to the
|
||||
// context of that particular event.
|
||||
// @param {Object=} room_info.third_party_invite Object containing data about the third party
|
||||
// we received to join the room, if any.
|
||||
// @param {string} thirdPartyInvite.inviteSignUrl 3pid invite sign URL
|
||||
// @param {string} thirdPartyInvite.invitedwithEmail The email address the invite was sent to
|
||||
// @param {Object} oob_data Object of additional data about the room
|
||||
// @param {string=} room_info.third_party_invite.inviteSignUrl 3pid invite sign URL
|
||||
// @param {string=} room_info.third_party_invite.invitedEmail The email address the invite was sent to
|
||||
// @param {Object=} room_info.oob_data Object of additional data about the room
|
||||
// that has been passed out-of-band (eg.
|
||||
// room name and avatar from an invite email)
|
||||
_viewRoom: function(roomId, roomAlias, showSettings, eventId, thirdPartyInvite, oob_data) {
|
||||
_viewRoom: function(room_info) {
|
||||
// before we switch room, record the scroll state of the current room
|
||||
this._updateScrollMap();
|
||||
|
||||
this.focusComposer = true;
|
||||
|
||||
var newState = {
|
||||
initialEventId: eventId,
|
||||
highlightedEventId: eventId,
|
||||
initialEventId: room_info.event_id,
|
||||
highlightedEventId: room_info.event_id,
|
||||
initialEventPixelOffset: undefined,
|
||||
page_type: this.PageTypes.RoomView,
|
||||
thirdPartyInvite: thirdPartyInvite,
|
||||
roomOobData: oob_data,
|
||||
currentRoomAlias: roomAlias,
|
||||
thirdPartyInvite: room_info.third_party_invite,
|
||||
roomOobData: room_info.oob_data,
|
||||
currentRoomAlias: room_info.room_alias,
|
||||
autoJoin: room_info.auto_join,
|
||||
};
|
||||
|
||||
if (!roomAlias) {
|
||||
newState.currentRoomId = roomId;
|
||||
if (!room_info.room_alias) {
|
||||
newState.currentRoomId = room_info.room_id;
|
||||
}
|
||||
|
||||
// if we aren't given an explicit event id, look for one in the
|
||||
// scrollStateMap.
|
||||
if (!eventId) {
|
||||
var scrollState = this.scrollStateMap[roomId];
|
||||
if (!room_info.event_id) {
|
||||
var scrollState = this.scrollStateMap[room_info.room_id];
|
||||
if (scrollState) {
|
||||
newState.initialEventId = scrollState.focussedEvent;
|
||||
newState.initialEventPixelOffset = scrollState.pixelOffset;
|
||||
@@ -538,8 +546,8 @@ module.exports = React.createClass({
|
||||
// the new screen yet (we won't be showing it yet)
|
||||
// The normal case where this happens is navigating
|
||||
// to the room in the URL bar on page load.
|
||||
var presentedId = roomAlias || roomId;
|
||||
var room = MatrixClientPeg.get().getRoom(roomId);
|
||||
var presentedId = room_info.room_alias || room_info.room_id;
|
||||
var room = MatrixClientPeg.get().getRoom(room_info.room_id);
|
||||
if (room) {
|
||||
var theAlias = MatrixTools.getDisplayAliasForRoom(room);
|
||||
if (theAlias) presentedId = theAlias;
|
||||
@@ -555,15 +563,15 @@ module.exports = React.createClass({
|
||||
// Tinter.tint(color_scheme.primary_color, color_scheme.secondary_color);
|
||||
}
|
||||
|
||||
if (eventId) {
|
||||
presentedId += "/"+eventId;
|
||||
if (room_info.event_id) {
|
||||
presentedId += "/"+room_info.event_id;
|
||||
}
|
||||
this.notifyNewScreen('room/'+presentedId);
|
||||
newState.ready = true;
|
||||
}
|
||||
this.setState(newState);
|
||||
|
||||
if (this.refs.roomView && showSettings) {
|
||||
if (this.refs.roomView && room_info.showSettings) {
|
||||
this.refs.roomView.showSettings(true);
|
||||
}
|
||||
},
|
||||
@@ -583,23 +591,36 @@ module.exports = React.createClass({
|
||||
this.scrollStateMap[roomId] = state;
|
||||
},
|
||||
|
||||
onLoggedIn: function(credentials) {
|
||||
credentials.guest = Boolean(credentials.guest);
|
||||
console.log("onLoggedIn => %s (guest=%s)", credentials.userId, credentials.guest);
|
||||
MatrixClientPeg.replaceUsingAccessToken(
|
||||
credentials.homeserverUrl, credentials.identityServerUrl,
|
||||
credentials.userId, credentials.accessToken, credentials.guest
|
||||
);
|
||||
/**
|
||||
* Called when a new logged in session has started
|
||||
*/
|
||||
_onLoggedIn: function(credentials) {
|
||||
this.guestCreds = null;
|
||||
this.notifyNewScreen('');
|
||||
this.setState({
|
||||
screen: undefined,
|
||||
logged_in: true
|
||||
logged_in: true,
|
||||
});
|
||||
this.startMatrixClient();
|
||||
this.notifyNewScreen('');
|
||||
},
|
||||
|
||||
startMatrixClient: function() {
|
||||
/**
|
||||
* Called when the session is logged out
|
||||
*/
|
||||
_onLoggedOut: function() {
|
||||
this.notifyNewScreen('login');
|
||||
this.replaceState({
|
||||
logged_in: false,
|
||||
ready: false,
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Called just before the matrix client is started
|
||||
* (useful for setting listeners)
|
||||
*/
|
||||
_onWillStartClient() {
|
||||
var cli = MatrixClientPeg.get();
|
||||
|
||||
var self = this;
|
||||
cli.on('sync', function(state, prevState) {
|
||||
self.updateFavicon(state, prevState);
|
||||
@@ -666,13 +687,6 @@ module.exports = React.createClass({
|
||||
action: 'logout'
|
||||
});
|
||||
});
|
||||
Notifier.start();
|
||||
UserActivity.start();
|
||||
Presence.start();
|
||||
cli.startClient({
|
||||
pendingEventOrdering: "detached",
|
||||
initialSyncLimit: this.props.config.sync_timeline_limit || 20,
|
||||
});
|
||||
},
|
||||
|
||||
// stop all the background processes related to the current client
|
||||
@@ -910,12 +924,14 @@ module.exports = React.createClass({
|
||||
|
||||
onReturnToGuestClick: function() {
|
||||
// reanimate our guest login
|
||||
this.onLoggedIn(this.state.guestCreds);
|
||||
this.setState({ guestCreds: null });
|
||||
if (this.guestCreds) {
|
||||
Lifecycle.setLoggedIn(this.guestCreds);
|
||||
this.guestCreds = null;
|
||||
}
|
||||
},
|
||||
|
||||
onRegistered: function(credentials) {
|
||||
this.onLoggedIn(credentials);
|
||||
Lifecycle.setLoggedIn(credentials);
|
||||
// do post-registration stuff
|
||||
// This now goes straight to user settings
|
||||
// We use _setPage since if we wait for
|
||||
@@ -1032,6 +1048,7 @@ module.exports = React.createClass({
|
||||
<RoomView
|
||||
ref="roomView"
|
||||
roomAddress={this.state.currentRoomAlias || this.state.currentRoomId}
|
||||
autoJoin={this.state.autoJoin}
|
||||
onRoomIdResolved={this.onRoomIdResolved}
|
||||
eventId={this.state.initialEventId}
|
||||
thirdPartyInvite={this.state.thirdPartyInvite}
|
||||
@@ -1111,8 +1128,8 @@ module.exports = React.createClass({
|
||||
email={this.props.startingQueryParams.email}
|
||||
username={this.state.upgradeUsername}
|
||||
guestAccessToken={this.state.guestAccessToken}
|
||||
defaultHsUrl={this.props.config.default_hs_url}
|
||||
defaultIsUrl={this.props.config.default_is_url}
|
||||
defaultHsUrl={this.getDefaultHsUrl()}
|
||||
defaultIsUrl={this.getDefaultIsUrl()}
|
||||
brand={this.props.config.brand}
|
||||
customHsUrl={this.getCurrentHsUrl()}
|
||||
customIsUrl={this.getCurrentIsUrl()}
|
||||
@@ -1120,14 +1137,14 @@ module.exports = React.createClass({
|
||||
onLoggedIn={this.onRegistered}
|
||||
onLoginClick={this.onLoginClick}
|
||||
onRegisterClick={this.onRegisterClick}
|
||||
onCancelClick={ this.state.guestCreds ? this.onReturnToGuestClick : null }
|
||||
onCancelClick={this.guestCreds ? this.onReturnToGuestClick : null}
|
||||
/>
|
||||
);
|
||||
} else if (this.state.screen == 'forgot_password') {
|
||||
return (
|
||||
<ForgotPassword
|
||||
defaultHsUrl={this.props.config.default_hs_url}
|
||||
defaultIsUrl={this.props.config.default_is_url}
|
||||
defaultHsUrl={this.getDefaultHsUrl()}
|
||||
defaultIsUrl={this.getDefaultIsUrl()}
|
||||
customHsUrl={this.getCurrentHsUrl()}
|
||||
customIsUrl={this.getCurrentIsUrl()}
|
||||
onComplete={this.onLoginClick}
|
||||
@@ -1136,16 +1153,16 @@ module.exports = React.createClass({
|
||||
} else {
|
||||
return (
|
||||
<Login
|
||||
onLoggedIn={this.onLoggedIn}
|
||||
onLoggedIn={Lifecycle.setLoggedIn}
|
||||
onRegisterClick={this.onRegisterClick}
|
||||
defaultHsUrl={this.props.config.default_hs_url}
|
||||
defaultIsUrl={this.props.config.default_is_url}
|
||||
defaultHsUrl={this.getDefaultHsUrl()}
|
||||
defaultIsUrl={this.getDefaultIsUrl()}
|
||||
customHsUrl={this.getCurrentHsUrl()}
|
||||
customIsUrl={this.getCurrentIsUrl()}
|
||||
fallbackHsUrl={this.getFallbackHsUrl()}
|
||||
onForgotPasswordClick={this.onForgotPasswordClick}
|
||||
onLoginAsGuestClick={this.props.enableGuest && this.props.config && this.props.config.default_hs_url ? this._registerAsGuest.bind(this, true) : undefined}
|
||||
onCancelClick={ this.state.guestCreds ? this.onReturnToGuestClick : null }
|
||||
onLoginAsGuestClick={this.props.enableGuest && this.props.config && this._registerAsGuest.bind(this, true)}
|
||||
onCancelClick={this.guestCreds ? this.onReturnToGuestClick : null}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -44,6 +44,9 @@ module.exports = React.createClass({
|
||||
// ID of an event to highlight. If undefined, no event will be highlighted.
|
||||
highlightedEventId: React.PropTypes.string,
|
||||
|
||||
// Should we show URL Previews
|
||||
showUrlPreview: React.PropTypes.bool,
|
||||
|
||||
// event after which we should show a read marker
|
||||
readMarkerEventId: React.PropTypes.string,
|
||||
|
||||
@@ -365,6 +368,7 @@ module.exports = React.createClass({
|
||||
onWidgetLoad={this._onWidgetLoad}
|
||||
readReceipts={readReceipts}
|
||||
readReceiptMap={this._readReceiptMap}
|
||||
showUrlPreview={this.props.showUrlPreview}
|
||||
checkUnmounting={this._isUnmounting}
|
||||
eventSendStatus={mxEv.status}
|
||||
last={last} isSelectedEvent={highlight}/>
|
||||
|
||||
@@ -26,9 +26,9 @@ module.exports = React.createClass({
|
||||
propTypes: {
|
||||
// the room this statusbar is representing.
|
||||
room: React.PropTypes.object.isRequired,
|
||||
|
||||
// a list of TabCompleteEntries.Entry objects
|
||||
tabCompleteEntries: React.PropTypes.array,
|
||||
|
||||
// a TabComplete object
|
||||
tabComplete: React.PropTypes.object.isRequired,
|
||||
|
||||
// the number of messages which have arrived since we've been scrolled up
|
||||
numUnreadMessages: React.PropTypes.number,
|
||||
@@ -208,11 +208,11 @@ module.exports = React.createClass({
|
||||
);
|
||||
}
|
||||
|
||||
if (this.props.tabCompleteEntries) {
|
||||
if (this.props.tabComplete.isTabCompleting()) {
|
||||
return (
|
||||
<div className="mx_RoomStatusBar_tabCompleteBar">
|
||||
<div className="mx_RoomStatusBar_tabCompleteWrapper">
|
||||
<TabCompleteBar entries={this.props.tabCompleteEntries} />
|
||||
<TabCompleteBar tabComplete={this.props.tabComplete} />
|
||||
<div className="mx_RoomStatusBar_tabCompleteEol" title="->|">
|
||||
<TintableSvg src="img/eol.svg" width="22" height="16"/>
|
||||
Auto-complete
|
||||
@@ -233,7 +233,7 @@ module.exports = React.createClass({
|
||||
<a className="mx_RoomStatusBar_resend_link"
|
||||
onClick={ this.props.onResendAllClick }>
|
||||
Resend all
|
||||
</a> or <a
|
||||
</a> or <a
|
||||
className="mx_RoomStatusBar_resend_link"
|
||||
onClick={ this.props.onCancelAllClick }>
|
||||
cancel all
|
||||
@@ -247,7 +247,7 @@ module.exports = React.createClass({
|
||||
// unread count trumps who is typing since the unread count is only
|
||||
// set when you've scrolled up
|
||||
if (this.props.numUnreadMessages) {
|
||||
var unreadMsgs = this.props.numUnreadMessages + " new message" +
|
||||
var unreadMsgs = this.props.numUnreadMessages + " new message" +
|
||||
(this.props.numUnreadMessages > 1 ? "s" : "");
|
||||
|
||||
return (
|
||||
@@ -291,5 +291,5 @@ module.exports = React.createClass({
|
||||
{content}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -31,16 +31,15 @@ var Modal = require("../../Modal");
|
||||
var sdk = require('../../index');
|
||||
var CallHandler = require('../../CallHandler');
|
||||
var TabComplete = require("../../TabComplete");
|
||||
var MemberEntry = require("../../TabCompleteEntries").MemberEntry;
|
||||
var CommandEntry = require("../../TabCompleteEntries").CommandEntry;
|
||||
var Resend = require("../../Resend");
|
||||
var SlashCommands = require("../../SlashCommands");
|
||||
var dis = require("../../dispatcher");
|
||||
var Tinter = require("../../Tinter");
|
||||
var rate_limited_func = require('../../ratelimitedfunc');
|
||||
var ObjectUtils = require('../../ObjectUtils');
|
||||
var MatrixTools = require('../../MatrixTools');
|
||||
|
||||
import UserProvider from '../../autocomplete/UserProvider';
|
||||
|
||||
var DEBUG = false;
|
||||
|
||||
if (DEBUG) {
|
||||
@@ -117,6 +116,11 @@ module.exports = React.createClass({
|
||||
guestsCanJoin: false,
|
||||
canPeek: false,
|
||||
|
||||
// error object, as from the matrix client/server API
|
||||
// If we failed to load information about the room,
|
||||
// store the error here.
|
||||
roomLoadError: null,
|
||||
|
||||
// this is true if we are fully scrolled-down, and are looking at
|
||||
// the end of the live timeline. It has the effect of hiding the
|
||||
// 'scroll to bottom' knob, among a couple of other things.
|
||||
@@ -134,6 +138,7 @@ module.exports = React.createClass({
|
||||
MatrixClientPeg.get().on("Room.timeline", this.onRoomTimeline);
|
||||
MatrixClientPeg.get().on("Room.accountData", this.onRoomAccountData);
|
||||
MatrixClientPeg.get().on("RoomState.members", this.onRoomStateMember);
|
||||
MatrixClientPeg.get().on("accountData", this.onAccountData);
|
||||
|
||||
this.tabComplete = new TabComplete({
|
||||
allowLooping: false,
|
||||
@@ -159,10 +164,11 @@ module.exports = React.createClass({
|
||||
roomId: result.room_id,
|
||||
roomLoading: !room,
|
||||
hasUnsentMessages: this._hasUnsentMessages(room),
|
||||
}, this._updatePeeking);
|
||||
}, this._onHaveRoom);
|
||||
}, (err) => {
|
||||
this.setState({
|
||||
roomLoading: false,
|
||||
roomLoadError: err,
|
||||
});
|
||||
});
|
||||
} else {
|
||||
@@ -172,11 +178,11 @@ module.exports = React.createClass({
|
||||
room: room,
|
||||
roomLoading: !room,
|
||||
hasUnsentMessages: this._hasUnsentMessages(room),
|
||||
}, this._updatePeeking);
|
||||
}, this._onHaveRoom);
|
||||
}
|
||||
},
|
||||
|
||||
_updatePeeking: function() {
|
||||
_onHaveRoom: function() {
|
||||
// if this is an unknown room then we're in one of three states:
|
||||
// - This is a room we can peek into (search engine) (we can /peek)
|
||||
// - This is a room we can publicly join or were invited to. (we can /join)
|
||||
@@ -187,29 +193,47 @@ module.exports = React.createClass({
|
||||
// Note that peeking works by room ID and room ID only, as opposed to joining
|
||||
// which must be by alias or invite wherever possible (peeking currently does
|
||||
// not work over federation).
|
||||
if (!this.state.room && this.state.roomId) {
|
||||
console.log("Attempting to peek into room %s", this.state.roomId);
|
||||
|
||||
MatrixClientPeg.get().peekInRoom(this.state.roomId).then((room) => {
|
||||
this.setState({
|
||||
room: room,
|
||||
roomLoading: false,
|
||||
});
|
||||
this._onRoomLoaded(room);
|
||||
}, (err) => {
|
||||
// This won't necessarily be a MatrixError, but we duck-type
|
||||
// here and say if it's got an 'errcode' key with the right value,
|
||||
// it means we can't peek.
|
||||
if (err.errcode == "M_GUEST_ACCESS_FORBIDDEN") {
|
||||
// This is fine: the room just isn't peekable (we assume).
|
||||
// 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'
|
||||
);
|
||||
|
||||
this._updateAutoComplete();
|
||||
this.tabComplete.loadEntries(this.state.room);
|
||||
}
|
||||
|
||||
if (!user_is_in_room && this.state.roomId) {
|
||||
if (this.props.autoJoin) {
|
||||
this.onJoinButtonClicked();
|
||||
} else if (this.state.roomId) {
|
||||
console.log("Attempting to peek into room %s", this.state.roomId);
|
||||
|
||||
MatrixClientPeg.get().peekInRoom(this.state.roomId).then((room) => {
|
||||
this.setState({
|
||||
room: room,
|
||||
roomLoading: false,
|
||||
});
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
}).done();
|
||||
} else if (this.state.room) {
|
||||
this._onRoomLoaded(room);
|
||||
}, (err) => {
|
||||
// This won't necessarily be a MatrixError, but we duck-type
|
||||
// here and say if it's got an 'errcode' key with the right value,
|
||||
// it means we can't peek.
|
||||
if (err.errcode == "M_GUEST_ACCESS_FORBIDDEN") {
|
||||
// This is fine: the room just isn't peekable (we assume).
|
||||
this.setState({
|
||||
roomLoading: false,
|
||||
});
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
}).done();
|
||||
}
|
||||
} else if (user_is_in_room) {
|
||||
MatrixClientPeg.get().stopPeeking();
|
||||
this._onRoomLoaded(this.state.room);
|
||||
}
|
||||
@@ -244,6 +268,7 @@ module.exports = React.createClass({
|
||||
MatrixClientPeg.get().removeListener("Room.timeline", this.onRoomTimeline);
|
||||
MatrixClientPeg.get().removeListener("Room.accountData", this.onRoomAccountData);
|
||||
MatrixClientPeg.get().removeListener("RoomState.members", this.onRoomStateMember);
|
||||
MatrixClientPeg.get().removeListener("accountData", this.onAccountData);
|
||||
}
|
||||
|
||||
window.removeEventListener('resize', this.onResize);
|
||||
@@ -315,6 +340,10 @@ module.exports = React.createClass({
|
||||
// ignore events for other rooms
|
||||
if (!this.state.room || room.roomId != this.state.room.roomId) return;
|
||||
|
||||
if (ev.getType() === "org.matrix.room.preview_urls") {
|
||||
this._updatePreviewUrlVisibility(room);
|
||||
}
|
||||
|
||||
// ignore anything but real-time updates at the end of the room:
|
||||
// updates from pagination will happen when the paginate completes.
|
||||
if (toStartOfTimeline || !data || !data.liveEvent) return;
|
||||
@@ -334,12 +363,21 @@ module.exports = React.createClass({
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// update the tab complete list as it depends on who most recently spoke,
|
||||
// and that has probably just changed
|
||||
if (ev.sender) {
|
||||
this.tabComplete.onMemberSpoke(ev.sender);
|
||||
// nb. we don't need to update the new autocomplete here since
|
||||
// its results are currently ordered purely by search score.
|
||||
}
|
||||
},
|
||||
|
||||
// called when state.room is first initialised (either at initial load,
|
||||
// after a successful peek, or after we join the room).
|
||||
_onRoomLoaded: function(room) {
|
||||
this._calculatePeekRules(room);
|
||||
this._updatePreviewUrlVisibility(room);
|
||||
},
|
||||
|
||||
_calculatePeekRules: function(room) {
|
||||
@@ -358,6 +396,42 @@ module.exports = React.createClass({
|
||||
}
|
||||
},
|
||||
|
||||
_updatePreviewUrlVisibility: function(room) {
|
||||
// console.log("_updatePreviewUrlVisibility");
|
||||
|
||||
// check our per-room overrides
|
||||
var roomPreviewUrls = room.getAccountData("org.matrix.room.preview_urls");
|
||||
if (roomPreviewUrls && roomPreviewUrls.getContent().disable !== undefined) {
|
||||
this.setState({
|
||||
showUrlPreview: !roomPreviewUrls.getContent().disable
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// check our global disable override
|
||||
var userRoomPreviewUrls = MatrixClientPeg.get().getAccountData("org.matrix.preview_urls");
|
||||
if (userRoomPreviewUrls && userRoomPreviewUrls.getContent().disable) {
|
||||
this.setState({
|
||||
showUrlPreview: false
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// check the room state event
|
||||
var roomStatePreviewUrls = room.currentState.getStateEvents('org.matrix.room.preview_urls', '');
|
||||
if (roomStatePreviewUrls && roomStatePreviewUrls.getContent().disable) {
|
||||
this.setState({
|
||||
showUrlPreview: false
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// otherwise, we assume they're on.
|
||||
this.setState({
|
||||
showUrlPreview: true
|
||||
});
|
||||
},
|
||||
|
||||
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
|
||||
@@ -388,14 +462,23 @@ module.exports = React.createClass({
|
||||
Tinter.tint(color_scheme.primary_color, color_scheme.secondary_color);
|
||||
},
|
||||
|
||||
onRoomAccountData: function(room, event) {
|
||||
if (room.roomId == this.props.roomId) {
|
||||
if (event.getType === "org.matrix.room.color_scheme") {
|
||||
onAccountData: function(event) {
|
||||
if (event.getType() === "org.matrix.preview_urls" && this.state.room) {
|
||||
this._updatePreviewUrlVisibility(this.state.room);
|
||||
}
|
||||
},
|
||||
|
||||
onRoomAccountData: function(event, room) {
|
||||
if (room.roomId == this.state.roomId) {
|
||||
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);
|
||||
}
|
||||
else if (event.getType() === "org.matrix.room.preview_urls") {
|
||||
this._updatePreviewUrlVisibility(room);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -410,8 +493,20 @@ module.exports = React.createClass({
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.props.ConferenceHandler &&
|
||||
member.userId === this.props.ConferenceHandler.getConferenceUserIdForRoom(member.roomId)) {
|
||||
this._updateConfCallNotification();
|
||||
}
|
||||
|
||||
this._updateRoomMembers();
|
||||
},
|
||||
|
||||
// rate limited because a power level change will emit an event for every
|
||||
// member in the room.
|
||||
_updateRoomMembers: new rate_limited_func(function() {
|
||||
// a member state changed in this room, refresh the tab complete list
|
||||
this._updateTabCompleteList();
|
||||
this.tabComplete.loadEntries(this.state.room);
|
||||
this._updateAutoComplete();
|
||||
|
||||
// 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
|
||||
@@ -422,12 +517,7 @@ module.exports = React.createClass({
|
||||
joining: false
|
||||
});
|
||||
}
|
||||
|
||||
if (this.props.ConferenceHandler &&
|
||||
member.userId === this.props.ConferenceHandler.getConferenceUserIdForRoom(member.roomId)) {
|
||||
this._updateConfCallNotification();
|
||||
}
|
||||
},
|
||||
}, 500),
|
||||
|
||||
_hasUnsentMessages: function(room) {
|
||||
return this._getUnsentMessages(room).length > 0;
|
||||
@@ -476,8 +566,6 @@ module.exports = React.createClass({
|
||||
window.addEventListener('resize', this.onResize);
|
||||
this.onResize();
|
||||
|
||||
this._updateTabCompleteList();
|
||||
|
||||
// XXX: EVIL HACK to autofocus inviting on empty rooms.
|
||||
// We use the setTimeout to avoid racing with focus_composer.
|
||||
if (this.state.room &&
|
||||
@@ -495,22 +583,6 @@ module.exports = React.createClass({
|
||||
}
|
||||
},
|
||||
|
||||
_updateTabCompleteList: new rate_limited_func(function() {
|
||||
var cli = MatrixClientPeg.get();
|
||||
|
||||
if (!this.state.room || !this.tabComplete) {
|
||||
return;
|
||||
}
|
||||
var members = this.state.room.getJoinedMembers().filter(function(member) {
|
||||
if (member.userId !== cli.credentials.userId) return true;
|
||||
});
|
||||
this.tabComplete.setCompletionList(
|
||||
MemberEntry.fromMemberList(members).concat(
|
||||
CommandEntry.fromCommands(SlashCommands.getCommandList())
|
||||
)
|
||||
);
|
||||
}, 500),
|
||||
|
||||
componentDidUpdate: function() {
|
||||
if (this.refs.roomView) {
|
||||
var roomView = ReactDOM.findDOMNode(this.refs.roomView);
|
||||
@@ -992,7 +1064,7 @@ module.exports = React.createClass({
|
||||
this.setState({
|
||||
rejecting: true
|
||||
});
|
||||
MatrixClientPeg.get().leave(this.props.roomAddress).done(function() {
|
||||
MatrixClientPeg.get().leave(this.state.roomId).done(function() {
|
||||
dis.dispatch({ action: 'view_next_room' });
|
||||
self.setState({
|
||||
rejecting: false
|
||||
@@ -1235,6 +1307,14 @@ module.exports = React.createClass({
|
||||
}
|
||||
},
|
||||
|
||||
_updateAutoComplete: function() {
|
||||
const myUserId = MatrixClientPeg.get().credentials.userId;
|
||||
const members = this.state.room.getJoinedMembers().filter(function(member) {
|
||||
if (member.userId !== myUserId) return true;
|
||||
});
|
||||
UserProvider.getInstance().setUserList(members);
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var RoomHeader = sdk.getComponent('rooms.RoomHeader');
|
||||
var MessageComposer = sdk.getComponent('rooms.MessageComposer');
|
||||
@@ -1267,6 +1347,7 @@ module.exports = React.createClass({
|
||||
|
||||
// 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"
|
||||
@@ -1277,7 +1358,8 @@ module.exports = React.createClass({
|
||||
<div className="mx_RoomView_auxPanel">
|
||||
<RoomPreviewBar onJoinClick={ this.onJoinButtonClicked }
|
||||
onRejectClick={ this.onRejectThreepidInviteButtonClicked }
|
||||
canJoin={ true } canPreview={ false }
|
||||
canPreview={ false } error={ this.state.roomLoadError }
|
||||
roomAlias={room_alias}
|
||||
spinner={this.state.joining}
|
||||
inviterName={inviterName}
|
||||
invitedEmail={invitedEmail}
|
||||
@@ -1315,7 +1397,7 @@ module.exports = React.createClass({
|
||||
<RoomPreviewBar onJoinClick={ this.onJoinButtonClicked }
|
||||
onRejectClick={ this.onRejectButtonClicked }
|
||||
inviterName={ inviterName }
|
||||
canJoin={ true } canPreview={ false }
|
||||
canPreview={ false }
|
||||
spinner={this.state.joining}
|
||||
room={this.state.room}
|
||||
/>
|
||||
@@ -1346,12 +1428,10 @@ module.exports = React.createClass({
|
||||
statusBar = <UploadBar room={this.state.room} />
|
||||
} else if (!this.state.searchResults) {
|
||||
var RoomStatusBar = sdk.getComponent('structures.RoomStatusBar');
|
||||
var tabEntries = this.tabComplete.isTabCompleting() ?
|
||||
this.tabComplete.peek(6) : null;
|
||||
|
||||
statusBar = <RoomStatusBar
|
||||
room={this.state.room}
|
||||
tabCompleteEntries={tabEntries}
|
||||
tabComplete={this.tabComplete}
|
||||
numUnreadMessages={this.state.numUnreadMessages}
|
||||
hasUnsentMessages={this.state.hasUnsentMessages}
|
||||
atEndOfLiveTimeline={this.state.atEndOfLiveTimeline}
|
||||
@@ -1385,7 +1465,7 @@ module.exports = React.createClass({
|
||||
invitedEmail = this.props.thirdPartyInvite.invitedEmail;
|
||||
}
|
||||
aux = (
|
||||
<RoomPreviewBar onJoinClick={this.onJoinButtonClicked} canJoin={true}
|
||||
<RoomPreviewBar onJoinClick={this.onJoinButtonClicked}
|
||||
onRejectClick={this.onRejectThreepidInviteButtonClicked}
|
||||
spinner={this.state.joining}
|
||||
inviterName={inviterName}
|
||||
@@ -1484,6 +1564,8 @@ module.exports = React.createClass({
|
||||
hideMessagePanel = true;
|
||||
}
|
||||
|
||||
// console.log("ShowUrlPreview for %s is %s", this.state.room.roomId, this.state.showUrlPreview);
|
||||
|
||||
var messagePanel = (
|
||||
<TimelinePanel ref={this._gatherTimelinePanelRef}
|
||||
room={this.state.room}
|
||||
@@ -1493,6 +1575,7 @@ module.exports = React.createClass({
|
||||
eventPixelOffset={this.props.eventPixelOffset}
|
||||
onScroll={ this.onMessageListScroll }
|
||||
onReadMarkerUpdated={ this._updateTopUnreadMessagesBar }
|
||||
showUrlPreview = { this.state.showUrlPreview }
|
||||
opacity={ this.props.opacity }
|
||||
/>);
|
||||
|
||||
|
||||
@@ -540,7 +540,6 @@ 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">
|
||||
|
||||
@@ -71,6 +71,9 @@ var TimelinePanel = React.createClass({
|
||||
// half way down the viewport.
|
||||
eventPixelOffset: React.PropTypes.number,
|
||||
|
||||
// Should we show URL Previews
|
||||
showUrlPreview: React.PropTypes.bool,
|
||||
|
||||
// callback which is called when the panel is scrolled.
|
||||
onScroll: React.PropTypes.func,
|
||||
|
||||
@@ -934,6 +937,7 @@ var TimelinePanel = React.createClass({
|
||||
readMarkerEventId={ this.state.readMarkerEventId }
|
||||
readMarkerVisible={ this.state.readMarkerVisible }
|
||||
suppressFirstDateSeparator={ this.state.canBackPaginate }
|
||||
showUrlPreview = { this.props.showUrlPreview }
|
||||
ourUserId={ MatrixClientPeg.get().credentials.userId }
|
||||
stickyBottom={ stickyBottom }
|
||||
onScroll={ this.onMessageListScroll }
|
||||
|
||||
@@ -214,9 +214,10 @@ module.exports = React.createClass({
|
||||
onFinished: this.onEmailDialogFinished,
|
||||
});
|
||||
}, (err) => {
|
||||
this.setState({email_add_pending: false});
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
title: "Unable to add email address",
|
||||
description: err.toString()
|
||||
description: err.message
|
||||
});
|
||||
});
|
||||
ReactDOM.findDOMNode(this.refs.add_threepid_input).blur();
|
||||
@@ -261,7 +262,64 @@ module.exports = React.createClass({
|
||||
});
|
||||
},
|
||||
|
||||
_renderDeviceInfo: function() {
|
||||
_renderUserInterfaceSettings: function() {
|
||||
var client = MatrixClientPeg.get();
|
||||
|
||||
var settingsLabels = [
|
||||
/*
|
||||
{
|
||||
id: 'alwaysShowTimestamps',
|
||||
label: 'Always show message timestamps',
|
||||
},
|
||||
{
|
||||
id: 'showTwelveHourTimestamps',
|
||||
label: 'Show timestamps in 12 hour format (e.g. 2:30pm)',
|
||||
},
|
||||
{
|
||||
id: 'useCompactLayout',
|
||||
label: 'Use compact timeline layout',
|
||||
},
|
||||
{
|
||||
id: 'useFixedWidthFont',
|
||||
label: 'Use fixed width font',
|
||||
},
|
||||
*/
|
||||
];
|
||||
|
||||
var syncedSettings = UserSettingsStore.getSyncedSettings();
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h3>User Interface</h3>
|
||||
<div className="mx_UserSettings_section">
|
||||
<div className="mx_UserSettings_toggle">
|
||||
<input id="urlPreviewsDisabled"
|
||||
type="checkbox"
|
||||
defaultChecked={ UserSettingsStore.getUrlPreviewsDisabled() }
|
||||
onChange={ e => UserSettingsStore.setUrlPreviewsDisabled(e.target.checked) }
|
||||
/>
|
||||
<label htmlFor="urlPreviewsDisabled">
|
||||
Disable inline URL previews by default
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
{ settingsLabels.forEach( setting => {
|
||||
<div className="mx_UserSettings_toggle">
|
||||
<input id={ setting.id }
|
||||
type="checkbox"
|
||||
defaultChecked={ syncedSettings[setting.id] }
|
||||
onChange={ e => UserSettingsStore.setSyncedSetting(setting.id, e.target.checked) }
|
||||
/>
|
||||
<label htmlFor={ setting.id }>
|
||||
{ settings.label }
|
||||
</label>
|
||||
</div>
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
|
||||
_renderCryptoInfo: function() {
|
||||
if (!UserSettingsStore.isFeatureEnabled("e2e_encryption")) {
|
||||
return null;
|
||||
}
|
||||
@@ -282,6 +340,45 @@ module.exports = React.createClass({
|
||||
);
|
||||
},
|
||||
|
||||
_renderDevicesPanel: function() {
|
||||
if (!UserSettingsStore.isFeatureEnabled("e2e_encryption")) {
|
||||
return null;
|
||||
}
|
||||
var DevicesPanel = sdk.getComponent('settings.DevicesPanel');
|
||||
return (
|
||||
<div>
|
||||
<h3>Devices</h3>
|
||||
<DevicesPanel className="mx_UserSettings_section" />
|
||||
</div>
|
||||
);
|
||||
},
|
||||
|
||||
_renderLabs: function () {
|
||||
let features = LABS_FEATURES.map(feature => (
|
||||
<div key={feature.id} className="mx_UserSettings_toggle">
|
||||
<input
|
||||
type="checkbox"
|
||||
id={feature.id}
|
||||
name={feature.id}
|
||||
defaultChecked={UserSettingsStore.isFeatureEnabled(feature.id)}
|
||||
onChange={e => {
|
||||
UserSettingsStore.setFeatureEnabled(feature.id, e.target.checked);
|
||||
this.forceUpdate();
|
||||
}}/>
|
||||
<label htmlFor={feature.id}>{feature.name}</label>
|
||||
</div>
|
||||
));
|
||||
return (
|
||||
<div>
|
||||
<h3>Labs</h3>
|
||||
<div className="mx_UserSettings_section">
|
||||
<p>These are experimental features that may break in unexpected ways. Use with caution.</p>
|
||||
{features}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var self = this;
|
||||
var Loader = sdk.getComponent("elements.Spinner");
|
||||
@@ -302,6 +399,7 @@ module.exports = React.createClass({
|
||||
var ChangeAvatar = sdk.getComponent('settings.ChangeAvatar');
|
||||
var Notifications = sdk.getComponent("settings.Notifications");
|
||||
var EditableText = sdk.getComponent('elements.EditableText');
|
||||
|
||||
var avatarUrl = (
|
||||
this.state.avatarUrl ? MatrixClientPeg.get().mxcUrlToHttp(this.state.avatarUrl) : null
|
||||
);
|
||||
@@ -376,36 +474,11 @@ module.exports = React.createClass({
|
||||
</div>);
|
||||
}
|
||||
|
||||
this._renderLabs = function () {
|
||||
let features = LABS_FEATURES.map(feature => (
|
||||
<div key={feature.id}>
|
||||
<input
|
||||
type="checkbox"
|
||||
id={feature.id}
|
||||
name={feature.id}
|
||||
defaultChecked={UserSettingsStore.isFeatureEnabled(feature.id)}
|
||||
onChange={e => UserSettingsStore.setFeatureEnabled(feature.id, e.target.checked)} />
|
||||
<label htmlFor={feature.id}>{feature.name}</label>
|
||||
</div>
|
||||
));
|
||||
return (
|
||||
<div>
|
||||
<h3>Labs</h3>
|
||||
|
||||
<div className="mx_UserSettings_section">
|
||||
<p>These are experimental features that may break in unexpected ways. Use with caution.</p>
|
||||
{features}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="mx_UserSettings">
|
||||
<SimpleRoomHeader title="Settings" onCancelClick={ this.props.onClose }/>
|
||||
|
||||
<GeminiScrollbar className="mx_UserSettings_body"
|
||||
relayoutOnUpdate={false}
|
||||
autoshow={true}>
|
||||
|
||||
<h3>Profile</h3>
|
||||
@@ -452,9 +525,10 @@ module.exports = React.createClass({
|
||||
|
||||
{notification_area}
|
||||
|
||||
{this._renderDeviceInfo()}
|
||||
|
||||
{this._renderUserInterfaceSettings()}
|
||||
{this._renderLabs()}
|
||||
{this._renderDevicesPanel()}
|
||||
{this._renderCryptoInfo()}
|
||||
|
||||
<h3>Advanced</h3>
|
||||
|
||||
|
||||
@@ -232,7 +232,9 @@ module.exports = React.createClass({displayName: 'Login',
|
||||
<div className="mx_Login_box">
|
||||
<LoginHeader />
|
||||
<div>
|
||||
<h2>Sign in</h2>
|
||||
<h2>Sign in
|
||||
{ loader }
|
||||
</h2>
|
||||
{ this.componentForStep(this._getCurrentFlowStep()) }
|
||||
<ServerConfig ref="serverConfig"
|
||||
withToggleButton={true}
|
||||
@@ -244,7 +246,6 @@ module.exports = React.createClass({displayName: 'Login',
|
||||
onIsUrlChanged={this.onIsUrlChanged}
|
||||
delayTimeMs={1000}/>
|
||||
<div className="mx_Login_error">
|
||||
{ loader }
|
||||
{ this.state.errorText }
|
||||
</div>
|
||||
<a className="mx_Login_create" onClick={this.props.onRegisterClick} href="#">
|
||||
|
||||
@@ -54,6 +54,16 @@ module.exports = React.createClass({
|
||||
return {
|
||||
busy: false,
|
||||
errorText: null,
|
||||
// We remember the values entered by the user because
|
||||
// the registration form will be unmounted during the
|
||||
// course of registration, but if there's an error we
|
||||
// want to bring back the registration form with the
|
||||
// values the user entered still in it. We can keep
|
||||
// them in this component's state since this component
|
||||
// persist for the duration of the registration process.
|
||||
formVals: {
|
||||
email: this.props.email,
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
@@ -108,7 +118,8 @@ module.exports = React.createClass({
|
||||
var self = this;
|
||||
this.setState({
|
||||
errorText: "",
|
||||
busy: true
|
||||
busy: true,
|
||||
formVals: formVals,
|
||||
});
|
||||
|
||||
if (formVals.username !== this.props.username) {
|
||||
@@ -228,11 +239,15 @@ module.exports = React.createClass({
|
||||
break; // NOP
|
||||
case "Register.START":
|
||||
case "Register.STEP_m.login.dummy":
|
||||
// NB. Our 'username' prop is specifically for upgrading
|
||||
// a guest account
|
||||
registerStep = (
|
||||
<RegistrationForm
|
||||
showEmail={true}
|
||||
defaultUsername={this.props.username}
|
||||
defaultEmail={this.props.email}
|
||||
defaultUsername={this.state.formVals.username}
|
||||
defaultEmail={this.state.formVals.email}
|
||||
defaultPassword={this.state.formVals.password}
|
||||
guestUsername={this.props.username}
|
||||
minPasswordLength={MIN_PASSWORD_LENGTH}
|
||||
onError={this.onFormValidationFailed}
|
||||
onRegisterClick={this.onFormSubmit} />
|
||||
|
||||
Reference in New Issue
Block a user