mirror of https://github.com/vector-im/riot-web
merge develop
commit
5f763f8ebb
|
@ -0,0 +1,51 @@
|
||||||
|
/*
|
||||||
|
Copyright 2015 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.
|
||||||
|
*/
|
||||||
|
const IS_GUEST_KEY = "matrix-is-guest";
|
||||||
|
|
||||||
|
class GuestAccess {
|
||||||
|
|
||||||
|
constructor(localStorage) {
|
||||||
|
this.localStorage = localStorage;
|
||||||
|
try {
|
||||||
|
this._isGuest = localStorage.getItem(IS_GUEST_KEY) === "true";
|
||||||
|
}
|
||||||
|
catch (e) {} // don't care
|
||||||
|
}
|
||||||
|
|
||||||
|
setPeekedRoom(roomId) {
|
||||||
|
// we purposefully do not persist this to local storage as peeking is
|
||||||
|
// entirely transient.
|
||||||
|
this._peekedRoomId = roomId;
|
||||||
|
}
|
||||||
|
|
||||||
|
getPeekedRoom() {
|
||||||
|
return this._peekedRoomId;
|
||||||
|
}
|
||||||
|
|
||||||
|
isGuest() {
|
||||||
|
return this._isGuest;
|
||||||
|
}
|
||||||
|
|
||||||
|
markAsGuest(isGuest) {
|
||||||
|
try {
|
||||||
|
this.localStorage.setItem(IS_GUEST_KEY, JSON.stringify(isGuest));
|
||||||
|
} catch (e) {} // ignore. If they don't do LS, they'll just get a new account.
|
||||||
|
this._isGuest = isGuest;
|
||||||
|
this._peekedRoomId = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = GuestAccess;
|
|
@ -18,6 +18,7 @@ limitations under the License.
|
||||||
|
|
||||||
// A thing that holds your Matrix Client
|
// A thing that holds your Matrix Client
|
||||||
var Matrix = require("matrix-js-sdk");
|
var Matrix = require("matrix-js-sdk");
|
||||||
|
var GuestAccess = require("./GuestAccess");
|
||||||
|
|
||||||
var matrixClient = null;
|
var matrixClient = null;
|
||||||
|
|
||||||
|
@ -33,7 +34,7 @@ function deviceId() {
|
||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
|
|
||||||
function createClient(hs_url, is_url, user_id, access_token) {
|
function createClient(hs_url, is_url, user_id, access_token, guestAccess) {
|
||||||
var opts = {
|
var opts = {
|
||||||
baseUrl: hs_url,
|
baseUrl: hs_url,
|
||||||
idBaseUrl: is_url,
|
idBaseUrl: is_url,
|
||||||
|
@ -47,6 +48,15 @@ function createClient(hs_url, is_url, user_id, access_token) {
|
||||||
}
|
}
|
||||||
|
|
||||||
matrixClient = Matrix.createClient(opts);
|
matrixClient = Matrix.createClient(opts);
|
||||||
|
if (guestAccess) {
|
||||||
|
console.log("Guest: %s", guestAccess.isGuest());
|
||||||
|
matrixClient.setGuest(guestAccess.isGuest());
|
||||||
|
var peekedRoomId = guestAccess.getPeekedRoom();
|
||||||
|
if (peekedRoomId) {
|
||||||
|
console.log("Peeking in room %s", peekedRoomId);
|
||||||
|
matrixClient.peekInRoom(peekedRoomId);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (localStorage) {
|
if (localStorage) {
|
||||||
|
@ -54,12 +64,18 @@ if (localStorage) {
|
||||||
var is_url = localStorage.getItem("mx_is_url") || 'https://matrix.org';
|
var is_url = localStorage.getItem("mx_is_url") || 'https://matrix.org';
|
||||||
var access_token = localStorage.getItem("mx_access_token");
|
var access_token = localStorage.getItem("mx_access_token");
|
||||||
var user_id = localStorage.getItem("mx_user_id");
|
var user_id = localStorage.getItem("mx_user_id");
|
||||||
|
var guestAccess = new GuestAccess(localStorage);
|
||||||
if (access_token && user_id && hs_url) {
|
if (access_token && user_id && hs_url) {
|
||||||
createClient(hs_url, is_url, user_id, access_token);
|
createClient(hs_url, is_url, user_id, access_token, guestAccess);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class MatrixClient {
|
class MatrixClient {
|
||||||
|
|
||||||
|
constructor(guestAccess) {
|
||||||
|
this.guestAccess = guestAccess;
|
||||||
|
}
|
||||||
|
|
||||||
get() {
|
get() {
|
||||||
return matrixClient;
|
return matrixClient;
|
||||||
}
|
}
|
||||||
|
@ -97,7 +113,7 @@ class MatrixClient {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
replaceUsingAccessToken(hs_url, is_url, user_id, access_token) {
|
replaceUsingAccessToken(hs_url, is_url, user_id, access_token, isGuest) {
|
||||||
if (localStorage) {
|
if (localStorage) {
|
||||||
try {
|
try {
|
||||||
localStorage.clear();
|
localStorage.clear();
|
||||||
|
@ -105,7 +121,8 @@ class MatrixClient {
|
||||||
console.warn("Error using local storage");
|
console.warn("Error using local storage");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
createClient(hs_url, is_url, user_id, access_token);
|
this.guestAccess.markAsGuest(Boolean(isGuest));
|
||||||
|
createClient(hs_url, is_url, user_id, access_token, this.guestAccess);
|
||||||
if (localStorage) {
|
if (localStorage) {
|
||||||
try {
|
try {
|
||||||
localStorage.setItem("mx_hs_url", hs_url);
|
localStorage.setItem("mx_hs_url", hs_url);
|
||||||
|
@ -122,6 +139,6 @@ class MatrixClient {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!global.mxMatrixClient) {
|
if (!global.mxMatrixClient) {
|
||||||
global.mxMatrixClient = new MatrixClient();
|
global.mxMatrixClient = new MatrixClient(new GuestAccess(localStorage));
|
||||||
}
|
}
|
||||||
module.exports = global.mxMatrixClient;
|
module.exports = global.mxMatrixClient;
|
||||||
|
|
|
@ -0,0 +1,104 @@
|
||||||
|
/*
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
var Matrix = require("matrix-js-sdk");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allows a user to reset their password on a homeserver.
|
||||||
|
*
|
||||||
|
* This involves getting an email token from the identity server to "prove" that
|
||||||
|
* the client owns the given email address, which is then passed to the password
|
||||||
|
* API on the homeserver in question with the new password.
|
||||||
|
*/
|
||||||
|
class PasswordReset {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configure the endpoints for password resetting.
|
||||||
|
* @param {string} homeserverUrl The URL to the HS which has the account to reset.
|
||||||
|
* @param {string} identityUrl The URL to the IS which has linked the email -> mxid mapping.
|
||||||
|
*/
|
||||||
|
constructor(homeserverUrl, identityUrl) {
|
||||||
|
this.client = Matrix.createClient({
|
||||||
|
baseUrl: homeserverUrl,
|
||||||
|
idBaseUrl: identityUrl
|
||||||
|
});
|
||||||
|
this.clientSecret = generateClientSecret();
|
||||||
|
this.identityServerDomain = identityUrl.split("://")[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attempt to reset the user's password. This will trigger a side-effect of
|
||||||
|
* sending an email to the provided email address.
|
||||||
|
* @param {string} emailAddress The email address
|
||||||
|
* @param {string} newPassword The new password for the account.
|
||||||
|
* @return {Promise} Resolves when the email has been sent. Then call checkEmailLinkClicked().
|
||||||
|
*/
|
||||||
|
resetPassword(emailAddress, newPassword) {
|
||||||
|
this.password = newPassword;
|
||||||
|
return this.client.requestEmailToken(emailAddress, this.clientSecret, 1).then((res) => {
|
||||||
|
this.sessionId = res.sid;
|
||||||
|
return res;
|
||||||
|
}, function(err) {
|
||||||
|
if (err.httpStatus) {
|
||||||
|
err.message = err.message + ` (Status ${err.httpStatus})`;
|
||||||
|
}
|
||||||
|
throw err;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the email link has been clicked by attempting to change the password
|
||||||
|
* for the mxid linked to the email.
|
||||||
|
* @return {Promise} Resolves if the password was reset. Rejects with an object
|
||||||
|
* with a "message" property which contains a human-readable message detailing why
|
||||||
|
* the reset failed, e.g. "There is no mapped matrix user ID for the given email address".
|
||||||
|
*/
|
||||||
|
checkEmailLinkClicked() {
|
||||||
|
return this.client.setPassword({
|
||||||
|
type: "m.login.email.identity",
|
||||||
|
threepid_creds: {
|
||||||
|
sid: this.sessionId,
|
||||||
|
client_secret: this.clientSecret,
|
||||||
|
id_server: this.identityServerDomain
|
||||||
|
}
|
||||||
|
}, this.password).catch(function(err) {
|
||||||
|
if (err.httpStatus === 401) {
|
||||||
|
err.message = "Failed to verify email address: make sure you clicked the link in the email";
|
||||||
|
}
|
||||||
|
else if (err.httpStatus === 404) {
|
||||||
|
err.message = "Your email address does not appear to be associated with a Matrix ID on this Homeserver.";
|
||||||
|
}
|
||||||
|
else if (err.httpStatus) {
|
||||||
|
err.message += ` (Status ${err.httpStatus})`;
|
||||||
|
}
|
||||||
|
throw err;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// from Angular SDK
|
||||||
|
function generateClientSecret() {
|
||||||
|
var ret = "";
|
||||||
|
var chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
||||||
|
|
||||||
|
for (var i = 0; i < 32; i++) {
|
||||||
|
ret += chars.charAt(Math.floor(Math.random() * chars.length));
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = PasswordReset;
|
|
@ -73,6 +73,11 @@ class Presence {
|
||||||
}
|
}
|
||||||
var old_state = this.state;
|
var old_state = this.state;
|
||||||
this.state = newState;
|
this.state = newState;
|
||||||
|
|
||||||
|
if (MatrixClientPeg.get().isGuest()) {
|
||||||
|
return; // don't try to set presence when a guest; it won't work.
|
||||||
|
}
|
||||||
|
|
||||||
var self = this;
|
var self = this;
|
||||||
MatrixClientPeg.get().setPresence(this.state).done(function() {
|
MatrixClientPeg.get().setPresence(this.state).done(function() {
|
||||||
console.log("Presence: %s", newState);
|
console.log("Presence: %s", newState);
|
||||||
|
|
|
@ -69,6 +69,10 @@ class Register extends Signup {
|
||||||
this.params.idSid = idSid;
|
this.params.idSid = idSid;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setGuestAccessToken(token) {
|
||||||
|
this.guestAccessToken = token;
|
||||||
|
}
|
||||||
|
|
||||||
getStep() {
|
getStep() {
|
||||||
return this._step;
|
return this._step;
|
||||||
}
|
}
|
||||||
|
@ -126,7 +130,8 @@ class Register extends Signup {
|
||||||
}
|
}
|
||||||
|
|
||||||
return MatrixClientPeg.get().register(
|
return MatrixClientPeg.get().register(
|
||||||
this.username, this.password, this.params.sessionId, authDict, bindEmail
|
this.username, this.password, this.params.sessionId, authDict, bindEmail,
|
||||||
|
this.guestAccessToken
|
||||||
).then(function(result) {
|
).then(function(result) {
|
||||||
self.credentials = result;
|
self.credentials = result;
|
||||||
self.setStep("COMPLETE");
|
self.setStep("COMPLETE");
|
||||||
|
|
|
@ -31,6 +31,11 @@ class UserActivity {
|
||||||
start() {
|
start() {
|
||||||
document.onmousemove = this._onUserActivity.bind(this);
|
document.onmousemove = this._onUserActivity.bind(this);
|
||||||
document.onkeypress = this._onUserActivity.bind(this);
|
document.onkeypress = this._onUserActivity.bind(this);
|
||||||
|
// can't use document.scroll here because that's only the document
|
||||||
|
// itself being scrolled. Need to use addEventListener's useCapture.
|
||||||
|
// also this needs to be the wheel event, not scroll, as scroll is
|
||||||
|
// fired when the view scrolls down for a new message.
|
||||||
|
window.addEventListener('wheel', this._onUserActivity.bind(this), true);
|
||||||
this.lastActivityAtTs = new Date().getTime();
|
this.lastActivityAtTs = new Date().getTime();
|
||||||
this.lastDispatchAtTs = 0;
|
this.lastDispatchAtTs = 0;
|
||||||
}
|
}
|
||||||
|
@ -41,10 +46,11 @@ class UserActivity {
|
||||||
stop() {
|
stop() {
|
||||||
document.onmousemove = undefined;
|
document.onmousemove = undefined;
|
||||||
document.onkeypress = undefined;
|
document.onkeypress = undefined;
|
||||||
|
window.removeEventListener('wheel', this._onUserActivity.bind(this), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
_onUserActivity(event) {
|
_onUserActivity(event) {
|
||||||
if (event.screenX) {
|
if (event.screenX && event.type == "mousemove") {
|
||||||
if (event.screenX === this.lastScreenX &&
|
if (event.screenX === this.lastScreenX &&
|
||||||
event.screenY === this.lastScreenY)
|
event.screenY === this.lastScreenY)
|
||||||
{
|
{
|
||||||
|
|
|
@ -15,7 +15,7 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
var q = require("q");
|
||||||
var MatrixClientPeg = require("./MatrixClientPeg");
|
var MatrixClientPeg = require("./MatrixClientPeg");
|
||||||
var Notifier = require("./Notifier");
|
var Notifier = require("./Notifier");
|
||||||
|
|
||||||
|
@ -35,6 +35,11 @@ module.exports = {
|
||||||
},
|
},
|
||||||
|
|
||||||
loadThreePids: function() {
|
loadThreePids: function() {
|
||||||
|
if (MatrixClientPeg.get().isGuest()) {
|
||||||
|
return q({
|
||||||
|
threepids: []
|
||||||
|
}); // guests can't poke 3pid endpoint
|
||||||
|
}
|
||||||
return MatrixClientPeg.get().getThreePids();
|
return MatrixClientPeg.get().getThreePids();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -36,7 +36,7 @@ module.exports = React.createClass({
|
||||||
var old = oldChildren[c.key];
|
var old = oldChildren[c.key];
|
||||||
var oldNode = ReactDom.findDOMNode(self.nodes[old.key]);
|
var oldNode = ReactDom.findDOMNode(self.nodes[old.key]);
|
||||||
|
|
||||||
if (oldNode.style.left != c.props.style.left) {
|
if (oldNode && oldNode.style.left != c.props.style.left) {
|
||||||
Velocity(oldNode, { left: c.props.style.left }, self.props.transition).then(function() {
|
Velocity(oldNode, { left: c.props.style.left }, self.props.transition).then(function() {
|
||||||
// special case visibility because it's nonsensical to animate an invisible element
|
// special case visibility because it's nonsensical to animate an invisible element
|
||||||
// so we always hidden->visible pre-transition and visible->hidden after
|
// so we always hidden->visible pre-transition and visible->hidden after
|
||||||
|
@ -73,6 +73,7 @@ module.exports = React.createClass({
|
||||||
|
|
||||||
collectNode: function(k, node) {
|
collectNode: function(k, node) {
|
||||||
if (
|
if (
|
||||||
|
node &&
|
||||||
this.nodes[k] === undefined &&
|
this.nodes[k] === undefined &&
|
||||||
node.props.startStyle &&
|
node.props.startStyle &&
|
||||||
Object.keys(node.props.startStyle).length
|
Object.keys(node.props.startStyle).length
|
||||||
|
|
|
@ -23,14 +23,15 @@ limitations under the License.
|
||||||
|
|
||||||
module.exports.components = {};
|
module.exports.components = {};
|
||||||
module.exports.components['structures.CreateRoom'] = require('./components/structures/CreateRoom');
|
module.exports.components['structures.CreateRoom'] = require('./components/structures/CreateRoom');
|
||||||
|
module.exports.components['structures.login.ForgotPassword'] = require('./components/structures/login/ForgotPassword');
|
||||||
|
module.exports.components['structures.login.Login'] = require('./components/structures/login/Login');
|
||||||
|
module.exports.components['structures.login.PostRegistration'] = require('./components/structures/login/PostRegistration');
|
||||||
|
module.exports.components['structures.login.Registration'] = require('./components/structures/login/Registration');
|
||||||
module.exports.components['structures.MatrixChat'] = require('./components/structures/MatrixChat');
|
module.exports.components['structures.MatrixChat'] = require('./components/structures/MatrixChat');
|
||||||
module.exports.components['structures.RoomView'] = require('./components/structures/RoomView');
|
module.exports.components['structures.RoomView'] = require('./components/structures/RoomView');
|
||||||
module.exports.components['structures.ScrollPanel'] = require('./components/structures/ScrollPanel');
|
module.exports.components['structures.ScrollPanel'] = require('./components/structures/ScrollPanel');
|
||||||
module.exports.components['structures.UploadBar'] = require('./components/structures/UploadBar');
|
module.exports.components['structures.UploadBar'] = require('./components/structures/UploadBar');
|
||||||
module.exports.components['structures.UserSettings'] = require('./components/structures/UserSettings');
|
module.exports.components['structures.UserSettings'] = require('./components/structures/UserSettings');
|
||||||
module.exports.components['structures.login.Login'] = require('./components/structures/login/Login');
|
|
||||||
module.exports.components['structures.login.PostRegistration'] = require('./components/structures/login/PostRegistration');
|
|
||||||
module.exports.components['structures.login.Registration'] = require('./components/structures/login/Registration');
|
|
||||||
module.exports.components['views.avatars.MemberAvatar'] = require('./components/views/avatars/MemberAvatar');
|
module.exports.components['views.avatars.MemberAvatar'] = require('./components/views/avatars/MemberAvatar');
|
||||||
module.exports.components['views.avatars.RoomAvatar'] = require('./components/views/avatars/RoomAvatar');
|
module.exports.components['views.avatars.RoomAvatar'] = require('./components/views/avatars/RoomAvatar');
|
||||||
module.exports.components['views.create_room.CreateRoomButton'] = require('./components/views/create_room/CreateRoomButton');
|
module.exports.components['views.create_room.CreateRoomButton'] = require('./components/views/create_room/CreateRoomButton');
|
||||||
|
@ -52,10 +53,10 @@ module.exports.components['views.login.LoginHeader'] = require('./components/vie
|
||||||
module.exports.components['views.login.PasswordLogin'] = require('./components/views/login/PasswordLogin');
|
module.exports.components['views.login.PasswordLogin'] = require('./components/views/login/PasswordLogin');
|
||||||
module.exports.components['views.login.RegistrationForm'] = require('./components/views/login/RegistrationForm');
|
module.exports.components['views.login.RegistrationForm'] = require('./components/views/login/RegistrationForm');
|
||||||
module.exports.components['views.login.ServerConfig'] = require('./components/views/login/ServerConfig');
|
module.exports.components['views.login.ServerConfig'] = require('./components/views/login/ServerConfig');
|
||||||
|
module.exports.components['views.messages.MessageEvent'] = require('./components/views/messages/MessageEvent');
|
||||||
module.exports.components['views.messages.MFileBody'] = require('./components/views/messages/MFileBody');
|
module.exports.components['views.messages.MFileBody'] = require('./components/views/messages/MFileBody');
|
||||||
module.exports.components['views.messages.MImageBody'] = require('./components/views/messages/MImageBody');
|
module.exports.components['views.messages.MImageBody'] = require('./components/views/messages/MImageBody');
|
||||||
module.exports.components['views.messages.MVideoBody'] = require('./components/views/messages/MVideoBody');
|
module.exports.components['views.messages.MVideoBody'] = require('./components/views/messages/MVideoBody');
|
||||||
module.exports.components['views.messages.MessageEvent'] = require('./components/views/messages/MessageEvent');
|
|
||||||
module.exports.components['views.messages.TextualBody'] = require('./components/views/messages/TextualBody');
|
module.exports.components['views.messages.TextualBody'] = require('./components/views/messages/TextualBody');
|
||||||
module.exports.components['views.messages.TextualEvent'] = require('./components/views/messages/TextualEvent');
|
module.exports.components['views.messages.TextualEvent'] = require('./components/views/messages/TextualEvent');
|
||||||
module.exports.components['views.messages.UnknownBody'] = require('./components/views/messages/UnknownBody');
|
module.exports.components['views.messages.UnknownBody'] = require('./components/views/messages/UnknownBody');
|
||||||
|
|
|
@ -43,6 +43,7 @@ module.exports = React.createClass({
|
||||||
ConferenceHandler: React.PropTypes.any,
|
ConferenceHandler: React.PropTypes.any,
|
||||||
onNewScreen: React.PropTypes.func,
|
onNewScreen: React.PropTypes.func,
|
||||||
registrationUrl: React.PropTypes.string,
|
registrationUrl: React.PropTypes.string,
|
||||||
|
enableGuest: React.PropTypes.bool,
|
||||||
startingQueryParams: React.PropTypes.object
|
startingQueryParams: React.PropTypes.object
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -84,8 +85,21 @@ module.exports = React.createClass({
|
||||||
},
|
},
|
||||||
|
|
||||||
componentDidMount: function() {
|
componentDidMount: function() {
|
||||||
|
this._autoRegisterAsGuest = false;
|
||||||
|
if (this.props.enableGuest) {
|
||||||
|
if (!this.props.config || !this.props.config.default_hs_url) {
|
||||||
|
console.error("Cannot enable guest access: No supplied config prop for HS/IS URLs");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this._autoRegisterAsGuest = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
this.dispatcherRef = dis.register(this.onAction);
|
this.dispatcherRef = dis.register(this.onAction);
|
||||||
if (this.state.logged_in) {
|
if (this.state.logged_in) {
|
||||||
|
// 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();
|
this.startMatrixClient();
|
||||||
}
|
}
|
||||||
this.focusComposer = false;
|
this.focusComposer = false;
|
||||||
|
@ -94,8 +108,11 @@ module.exports = React.createClass({
|
||||||
this.scrollStateMap = {};
|
this.scrollStateMap = {};
|
||||||
document.addEventListener("keydown", this.onKeyDown);
|
document.addEventListener("keydown", this.onKeyDown);
|
||||||
window.addEventListener("focus", this.onFocus);
|
window.addEventListener("focus", this.onFocus);
|
||||||
|
|
||||||
if (this.state.logged_in) {
|
if (this.state.logged_in) {
|
||||||
this.notifyNewScreen('');
|
this.notifyNewScreen('');
|
||||||
|
} else if (this._autoRegisterAsGuest) {
|
||||||
|
this._registerAsGuest();
|
||||||
} else {
|
} else {
|
||||||
this.notifyNewScreen('login');
|
this.notifyNewScreen('login');
|
||||||
}
|
}
|
||||||
|
@ -127,6 +144,34 @@ module.exports = React.createClass({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
_registerAsGuest: function() {
|
||||||
|
var self = this;
|
||||||
|
var config = this.props.config;
|
||||||
|
console.log("Doing guest login on %s", config.default_hs_url);
|
||||||
|
MatrixClientPeg.replaceUsingUrls(
|
||||||
|
config.default_hs_url, config.default_is_url
|
||||||
|
);
|
||||||
|
MatrixClientPeg.get().registerGuest().done(function(creds) {
|
||||||
|
console.log("Registered as guest: %s", creds.user_id);
|
||||||
|
self._setAutoRegisterAsGuest(false);
|
||||||
|
self.onLoggedIn({
|
||||||
|
userId: creds.user_id,
|
||||||
|
accessToken: creds.access_token,
|
||||||
|
homeserverUrl: config.default_hs_url,
|
||||||
|
identityServerUrl: config.default_is_url,
|
||||||
|
guest: true
|
||||||
|
});
|
||||||
|
}, function(err) {
|
||||||
|
console.error(err.data);
|
||||||
|
self._setAutoRegisterAsGuest(false);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
_setAutoRegisterAsGuest: function(shouldAutoRegister) {
|
||||||
|
this._autoRegisterAsGuest = shouldAutoRegister;
|
||||||
|
this.forceUpdate();
|
||||||
|
},
|
||||||
|
|
||||||
onAction: function(payload) {
|
onAction: function(payload) {
|
||||||
var roomIndexDelta = 1;
|
var roomIndexDelta = 1;
|
||||||
|
|
||||||
|
@ -181,6 +226,21 @@ module.exports = React.createClass({
|
||||||
screen: 'post_registration'
|
screen: 'post_registration'
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
|
case 'start_upgrade_registration':
|
||||||
|
this.replaceState({
|
||||||
|
screen: "register",
|
||||||
|
upgradeUsername: MatrixClientPeg.get().getUserIdLocalpart(),
|
||||||
|
guestAccessToken: MatrixClientPeg.get().getAccessToken()
|
||||||
|
});
|
||||||
|
this.notifyNewScreen('register');
|
||||||
|
break;
|
||||||
|
case 'start_password_recovery':
|
||||||
|
if (this.state.logged_in) return;
|
||||||
|
this.replaceState({
|
||||||
|
screen: 'forgot_password'
|
||||||
|
});
|
||||||
|
this.notifyNewScreen('forgot_password');
|
||||||
|
break;
|
||||||
case 'token_login':
|
case 'token_login':
|
||||||
if (this.state.logged_in) return;
|
if (this.state.logged_in) return;
|
||||||
|
|
||||||
|
@ -392,10 +452,11 @@ module.exports = React.createClass({
|
||||||
},
|
},
|
||||||
|
|
||||||
onLoggedIn: function(credentials) {
|
onLoggedIn: function(credentials) {
|
||||||
console.log("onLoggedIn => %s", credentials.userId);
|
credentials.guest = Boolean(credentials.guest);
|
||||||
|
console.log("onLoggedIn => %s (guest=%s)", credentials.userId, credentials.guest);
|
||||||
MatrixClientPeg.replaceUsingAccessToken(
|
MatrixClientPeg.replaceUsingAccessToken(
|
||||||
credentials.homeserverUrl, credentials.identityServerUrl,
|
credentials.homeserverUrl, credentials.identityServerUrl,
|
||||||
credentials.userId, credentials.accessToken
|
credentials.userId, credentials.accessToken, credentials.guest
|
||||||
);
|
);
|
||||||
this.setState({
|
this.setState({
|
||||||
screen: undefined,
|
screen: undefined,
|
||||||
|
@ -515,6 +576,11 @@ module.exports = React.createClass({
|
||||||
action: 'token_login',
|
action: 'token_login',
|
||||||
params: params
|
params: params
|
||||||
});
|
});
|
||||||
|
} else if (screen == 'forgot_password') {
|
||||||
|
dis.dispatch({
|
||||||
|
action: 'start_password_recovery',
|
||||||
|
params: params
|
||||||
|
});
|
||||||
} else if (screen == 'new') {
|
} else if (screen == 'new') {
|
||||||
dis.dispatch({
|
dis.dispatch({
|
||||||
action: 'view_create_room',
|
action: 'view_create_room',
|
||||||
|
@ -624,6 +690,10 @@ module.exports = React.createClass({
|
||||||
this.showScreen("login");
|
this.showScreen("login");
|
||||||
},
|
},
|
||||||
|
|
||||||
|
onForgotPasswordClick: function() {
|
||||||
|
this.showScreen("forgot_password");
|
||||||
|
},
|
||||||
|
|
||||||
onRegistered: function(credentials) {
|
onRegistered: function(credentials) {
|
||||||
this.onLoggedIn(credentials);
|
this.onLoggedIn(credentials);
|
||||||
// do post-registration stuff
|
// do post-registration stuff
|
||||||
|
@ -662,6 +732,7 @@ module.exports = React.createClass({
|
||||||
var CreateRoom = sdk.getComponent('structures.CreateRoom');
|
var CreateRoom = sdk.getComponent('structures.CreateRoom');
|
||||||
var RoomDirectory = sdk.getComponent('structures.RoomDirectory');
|
var RoomDirectory = sdk.getComponent('structures.RoomDirectory');
|
||||||
var MatrixToolbar = sdk.getComponent('globals.MatrixToolbar');
|
var MatrixToolbar = sdk.getComponent('globals.MatrixToolbar');
|
||||||
|
var ForgotPassword = sdk.getComponent('structures.login.ForgotPassword');
|
||||||
|
|
||||||
// needs to be before normal PageTypes as you are logged in technically
|
// needs to be before normal PageTypes as you are logged in technically
|
||||||
if (this.state.screen == 'post_registration') {
|
if (this.state.screen == 'post_registration') {
|
||||||
|
@ -725,12 +796,20 @@ module.exports = React.createClass({
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else if (this.state.logged_in) {
|
} else if (this.state.logged_in || (!this.state.logged_in && this._autoRegisterAsGuest)) {
|
||||||
var Spinner = sdk.getComponent('elements.Spinner');
|
var Spinner = sdk.getComponent('elements.Spinner');
|
||||||
|
var logoutLink;
|
||||||
|
if (this.state.logged_in) {
|
||||||
|
logoutLink = (
|
||||||
|
<a href="#" className="mx_MatrixChat_splashButtons" onClick={ this.onLogoutClick }>
|
||||||
|
Logout
|
||||||
|
</a>
|
||||||
|
);
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<div className="mx_MatrixChat_splash">
|
<div className="mx_MatrixChat_splash">
|
||||||
<Spinner />
|
<Spinner />
|
||||||
<a href="#" className="mx_MatrixChat_splashButtons" onClick={ this.onLogoutClick }>Logout</a>
|
{logoutLink}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
} else if (this.state.screen == 'register') {
|
} else if (this.state.screen == 'register') {
|
||||||
|
@ -740,19 +819,30 @@ module.exports = React.createClass({
|
||||||
sessionId={this.state.register_session_id}
|
sessionId={this.state.register_session_id}
|
||||||
idSid={this.state.register_id_sid}
|
idSid={this.state.register_id_sid}
|
||||||
email={this.props.startingQueryParams.email}
|
email={this.props.startingQueryParams.email}
|
||||||
|
username={this.state.upgradeUsername}
|
||||||
|
disableUsernameChanges={Boolean(this.state.upgradeUsername)}
|
||||||
|
guestAccessToken={this.state.guestAccessToken}
|
||||||
hsUrl={this.props.config.default_hs_url}
|
hsUrl={this.props.config.default_hs_url}
|
||||||
isUrl={this.props.config.default_is_url}
|
isUrl={this.props.config.default_is_url}
|
||||||
registrationUrl={this.props.registrationUrl}
|
registrationUrl={this.props.registrationUrl}
|
||||||
onLoggedIn={this.onRegistered}
|
onLoggedIn={this.onRegistered}
|
||||||
onLoginClick={this.onLoginClick} />
|
onLoginClick={this.onLoginClick} />
|
||||||
);
|
);
|
||||||
|
} else if (this.state.screen == 'forgot_password') {
|
||||||
|
return (
|
||||||
|
<ForgotPassword
|
||||||
|
homeserverUrl={this.props.config.default_hs_url}
|
||||||
|
identityServerUrl={this.props.config.default_is_url}
|
||||||
|
onComplete={this.onLoginClick} />
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
return (
|
return (
|
||||||
<Login
|
<Login
|
||||||
onLoggedIn={this.onLoggedIn}
|
onLoggedIn={this.onLoggedIn}
|
||||||
onRegisterClick={this.onRegisterClick}
|
onRegisterClick={this.onRegisterClick}
|
||||||
homeserverUrl={this.props.config.default_hs_url}
|
homeserverUrl={this.props.config.default_hs_url}
|
||||||
identityServerUrl={this.props.config.default_is_url} />
|
identityServerUrl={this.props.config.default_is_url}
|
||||||
|
onForgotPasswordClick={this.onForgotPasswordClick} />
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,6 +41,7 @@ var Tinter = require("../../Tinter");
|
||||||
|
|
||||||
var PAGINATE_SIZE = 20;
|
var PAGINATE_SIZE = 20;
|
||||||
var INITIAL_SIZE = 20;
|
var INITIAL_SIZE = 20;
|
||||||
|
var SEND_READ_RECEIPT_DELAY = 2000;
|
||||||
|
|
||||||
var DEBUG_SCROLL = false;
|
var DEBUG_SCROLL = false;
|
||||||
|
|
||||||
|
@ -75,6 +76,8 @@ module.exports = React.createClass({
|
||||||
syncState: MatrixClientPeg.get().getSyncState(),
|
syncState: MatrixClientPeg.get().getSyncState(),
|
||||||
hasUnsentMessages: this._hasUnsentMessages(room),
|
hasUnsentMessages: this._hasUnsentMessages(room),
|
||||||
callState: null,
|
callState: null,
|
||||||
|
readMarkerEventId: room.getEventReadUpTo(MatrixClientPeg.get().credentials.userId),
|
||||||
|
readMarkerGhostEventId: undefined,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -99,9 +102,33 @@ module.exports = React.createClass({
|
||||||
this.forceUpdate();
|
this.forceUpdate();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
// 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)
|
||||||
|
// - This is a room we cannot join at all. (no action can help us)
|
||||||
|
// We can't try to /join because this may implicitly accept invites (!)
|
||||||
|
// We can /peek though. If it fails then we present the join UI. If it
|
||||||
|
// succeeds then great, show the preview (but we still may be able to /join!).
|
||||||
|
if (!this.state.room) {
|
||||||
|
console.log("Attempting to peek into room %s", this.props.roomId);
|
||||||
|
MatrixClientPeg.get().peekInRoom(this.props.roomId).done(function() {
|
||||||
|
// we don't need to do anything - JS SDK will emit Room events
|
||||||
|
// which will update the UI.
|
||||||
|
}, function(err) {
|
||||||
|
console.error("Failed to peek into room: %s", err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
componentWillUnmount: function() {
|
componentWillUnmount: function() {
|
||||||
|
// set a boolean to say we've been unmounted, which any pending
|
||||||
|
// promises can use to throw away their results.
|
||||||
|
//
|
||||||
|
// (We could use isMounted, but facebook have deprecated that.)
|
||||||
|
this.unmounted = true;
|
||||||
|
|
||||||
if (this.refs.messagePanel) {
|
if (this.refs.messagePanel) {
|
||||||
// disconnect the D&D event listeners from the message panel. This
|
// disconnect the D&D event listeners from the message panel. This
|
||||||
// is really just for hygiene - the messagePanel is going to be
|
// is really just for hygiene - the messagePanel is going to be
|
||||||
|
@ -201,7 +228,7 @@ module.exports = React.createClass({
|
||||||
},*/
|
},*/
|
||||||
|
|
||||||
onRoomTimeline: function(ev, room, toStartOfTimeline) {
|
onRoomTimeline: function(ev, room, toStartOfTimeline) {
|
||||||
if (!this.isMounted()) return;
|
if (this.unmounted) return;
|
||||||
|
|
||||||
// ignore anything that comes in whilst paginating: we get one
|
// ignore anything that comes in whilst paginating: we get one
|
||||||
// event for each new matrix event so this would cause a huge
|
// event for each new matrix event so this would cause a huge
|
||||||
|
@ -265,7 +292,33 @@ module.exports = React.createClass({
|
||||||
|
|
||||||
onRoomReceipt: function(receiptEvent, room) {
|
onRoomReceipt: function(receiptEvent, room) {
|
||||||
if (room.roomId == this.props.roomId) {
|
if (room.roomId == this.props.roomId) {
|
||||||
this.forceUpdate();
|
var readMarkerEventId = this.state.room.getEventReadUpTo(MatrixClientPeg.get().credentials.userId);
|
||||||
|
var readMarkerGhostEventId = this.state.readMarkerGhostEventId;
|
||||||
|
if (this.state.readMarkerEventId !== undefined && this.state.readMarkerEventId != readMarkerEventId) {
|
||||||
|
readMarkerGhostEventId = this.state.readMarkerEventId;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// if the event after the one referenced in the read receipt if sent by us, do nothing since
|
||||||
|
// this is a temporary period before the synthesized receipt for our own message arrives
|
||||||
|
var readMarkerGhostEventIndex;
|
||||||
|
for (var i = 0; i < room.timeline.length; ++i) {
|
||||||
|
if (room.timeline[i].getId() == readMarkerGhostEventId) {
|
||||||
|
readMarkerGhostEventIndex = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (readMarkerGhostEventIndex + 1 < room.timeline.length) {
|
||||||
|
var nextEvent = room.timeline[readMarkerGhostEventIndex + 1];
|
||||||
|
if (nextEvent.sender && nextEvent.sender.userId == MatrixClientPeg.get().credentials.userId) {
|
||||||
|
readMarkerGhostEventId = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
readMarkerEventId: readMarkerEventId,
|
||||||
|
readMarkerGhostEventId: readMarkerGhostEventId,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -383,11 +436,14 @@ module.exports = React.createClass({
|
||||||
_paginateCompleted: function() {
|
_paginateCompleted: function() {
|
||||||
debuglog("paginate complete");
|
debuglog("paginate complete");
|
||||||
|
|
||||||
this.setState({
|
// we might have switched rooms since the paginate started - just bin
|
||||||
room: MatrixClientPeg.get().getRoom(this.props.roomId)
|
// the results if so.
|
||||||
});
|
if (this.unmounted) return;
|
||||||
|
|
||||||
this.setState({paginating: false});
|
this.setState({
|
||||||
|
room: MatrixClientPeg.get().getRoom(this.props.roomId),
|
||||||
|
paginating: false,
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
onSearchResultsFillRequest: function(backwards) {
|
onSearchResultsFillRequest: function(backwards) {
|
||||||
|
@ -452,6 +508,12 @@ module.exports = React.createClass({
|
||||||
joining: false,
|
joining: false,
|
||||||
joinError: error
|
joinError: error
|
||||||
});
|
});
|
||||||
|
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
|
||||||
|
});
|
||||||
});
|
});
|
||||||
this.setState({
|
this.setState({
|
||||||
joining: true
|
joining: true
|
||||||
|
@ -565,7 +627,7 @@ module.exports = React.createClass({
|
||||||
|
|
||||||
return searchPromise.then(function(results) {
|
return searchPromise.then(function(results) {
|
||||||
debuglog("search complete");
|
debuglog("search complete");
|
||||||
if (!self.state.searching || self.searchId != localSearchId) {
|
if (self.unmounted || !self.state.searching || self.searchId != localSearchId) {
|
||||||
console.error("Discarding stale search results");
|
console.error("Discarding stale search results");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -583,7 +645,8 @@ module.exports = React.createClass({
|
||||||
|
|
||||||
// For overlapping highlights,
|
// For overlapping highlights,
|
||||||
// favour longer (more specific) terms first
|
// favour longer (more specific) terms first
|
||||||
highlights = highlights.sort(function(a, b) { b.length - a.length });
|
highlights = highlights.sort(function(a, b) {
|
||||||
|
return b.length - a.length });
|
||||||
|
|
||||||
self.setState({
|
self.setState({
|
||||||
searchHighlights: highlights,
|
searchHighlights: highlights,
|
||||||
|
@ -678,10 +741,10 @@ module.exports = React.createClass({
|
||||||
|
|
||||||
var EventTile = sdk.getComponent('rooms.EventTile');
|
var EventTile = sdk.getComponent('rooms.EventTile');
|
||||||
|
|
||||||
|
|
||||||
var prevEvent = null; // the last event we showed
|
var prevEvent = null; // the last event we showed
|
||||||
var readReceiptEventId = this.state.room.getEventReadUpTo(MatrixClientPeg.get().credentials.userId);
|
|
||||||
var startIdx = Math.max(0, this.state.room.timeline.length - this.state.messageCap);
|
var startIdx = Math.max(0, this.state.room.timeline.length - this.state.messageCap);
|
||||||
|
var readMarkerIndex;
|
||||||
|
var ghostIndex;
|
||||||
for (var i = startIdx; i < this.state.room.timeline.length; i++) {
|
for (var i = startIdx; i < this.state.room.timeline.length; i++) {
|
||||||
var mxEv = this.state.room.timeline[i];
|
var mxEv = this.state.room.timeline[i];
|
||||||
|
|
||||||
|
@ -695,6 +758,25 @@ module.exports = React.createClass({
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// now we've decided whether or not to show this message,
|
||||||
|
// add the read up to marker if appropriate
|
||||||
|
// doing this here means we implicitly do not show the marker
|
||||||
|
// if it's at the bottom
|
||||||
|
// NB. it would be better to decide where the read marker was going
|
||||||
|
// when the state changed rather than here in the render method, but
|
||||||
|
// this is where we decide what messages we show so it's the only
|
||||||
|
// place we know whether we're at the bottom or not.
|
||||||
|
var self = this;
|
||||||
|
var mxEvSender = mxEv.sender ? mxEv.sender.userId : null;
|
||||||
|
if (prevEvent && prevEvent.getId() == this.state.readMarkerEventId && mxEvSender != MatrixClientPeg.get().credentials.userId) {
|
||||||
|
var hr;
|
||||||
|
hr = (<hr className="mx_RoomView_myReadMarker" style={{opacity: 1, width: '99%'}} ref={function(n) {
|
||||||
|
self.readMarkerNode = n;
|
||||||
|
}} />);
|
||||||
|
readMarkerIndex = ret.length;
|
||||||
|
ret.push(<li key="_readupto" className="mx_RoomView_myReadMarker_container">{hr}</li>);
|
||||||
|
}
|
||||||
|
|
||||||
// is this a continuation of the previous message?
|
// is this a continuation of the previous message?
|
||||||
var continuation = false;
|
var continuation = false;
|
||||||
if (prevEvent !== null) {
|
if (prevEvent !== null) {
|
||||||
|
@ -731,17 +813,33 @@ module.exports = React.createClass({
|
||||||
</li>
|
</li>
|
||||||
);
|
);
|
||||||
|
|
||||||
if (eventId == readReceiptEventId) {
|
// A read up to marker has died and returned as a ghost!
|
||||||
ret.push(<hr className="mx_RoomView_myReadMarker" />);
|
// Lives in the dom as the ghost of the previous one while it fades away
|
||||||
|
if (eventId == this.state.readMarkerGhostEventId) {
|
||||||
|
ghostIndex = ret.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
prevEvent = mxEv;
|
prevEvent = mxEv;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// splice the read marker ghost in now that we know whether the read receipt
|
||||||
|
// is the last element or not, because we only decide as we're going along.
|
||||||
|
if (readMarkerIndex === undefined && ghostIndex && ghostIndex <= ret.length) {
|
||||||
|
var hr;
|
||||||
|
hr = (<hr className="mx_RoomView_myReadMarker" style={{opacity: 1, width: '85%'}} ref={function(n) {
|
||||||
|
Velocity(n, {opacity: '0', width: '10%'}, {duration: 400, easing: 'easeInSine', delay: 1000, complete: function() {
|
||||||
|
self.setState({readMarkerGhostEventId: undefined});
|
||||||
|
}});
|
||||||
|
}} />);
|
||||||
|
ret.splice(ghostIndex, 0, (
|
||||||
|
<li key="_readuptoghost" className="mx_RoomView_myReadMarker_container">{hr}</li>
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
},
|
},
|
||||||
|
|
||||||
uploadNewState: function(new_name, new_topic, new_join_rule, new_history_visibility, new_power_levels, new_color_scheme) {
|
uploadNewState: function(newVals) {
|
||||||
var old_name = this.state.room.name;
|
var old_name = this.state.room.name;
|
||||||
|
|
||||||
var old_topic = this.state.room.currentState.getStateEvents('m.room.topic', '');
|
var old_topic = this.state.room.currentState.getStateEvents('m.room.topic', '');
|
||||||
|
@ -767,54 +865,63 @@ module.exports = React.createClass({
|
||||||
|
|
||||||
var deferreds = [];
|
var deferreds = [];
|
||||||
|
|
||||||
if (old_name != new_name && new_name != undefined) {
|
if (old_name != newVals.name && newVals.name != undefined) {
|
||||||
deferreds.push(
|
deferreds.push(
|
||||||
MatrixClientPeg.get().setRoomName(this.state.room.roomId, new_name)
|
MatrixClientPeg.get().setRoomName(this.state.room.roomId, newVals.name)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (old_topic != new_topic && new_topic != undefined) {
|
if (old_topic != newVals.topic && newVals.topic != undefined) {
|
||||||
deferreds.push(
|
deferreds.push(
|
||||||
MatrixClientPeg.get().setRoomTopic(this.state.room.roomId, new_topic)
|
MatrixClientPeg.get().setRoomTopic(this.state.room.roomId, newVals.topic)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (old_join_rule != new_join_rule && new_join_rule != undefined) {
|
if (old_join_rule != newVals.join_rule && newVals.join_rule != undefined) {
|
||||||
deferreds.push(
|
deferreds.push(
|
||||||
MatrixClientPeg.get().sendStateEvent(
|
MatrixClientPeg.get().sendStateEvent(
|
||||||
this.state.room.roomId, "m.room.join_rules", {
|
this.state.room.roomId, "m.room.join_rules", {
|
||||||
join_rule: new_join_rule,
|
join_rule: newVals.join_rule,
|
||||||
}, ""
|
}, ""
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (old_history_visibility != new_history_visibility && new_history_visibility != undefined) {
|
if (old_history_visibility != newVals.history_visibility &&
|
||||||
|
newVals.history_visibility != undefined) {
|
||||||
deferreds.push(
|
deferreds.push(
|
||||||
MatrixClientPeg.get().sendStateEvent(
|
MatrixClientPeg.get().sendStateEvent(
|
||||||
this.state.room.roomId, "m.room.history_visibility", {
|
this.state.room.roomId, "m.room.history_visibility", {
|
||||||
history_visibility: new_history_visibility,
|
history_visibility: newVals.history_visibility,
|
||||||
}, ""
|
}, ""
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (new_power_levels) {
|
if (newVals.power_levels) {
|
||||||
deferreds.push(
|
deferreds.push(
|
||||||
MatrixClientPeg.get().sendStateEvent(
|
MatrixClientPeg.get().sendStateEvent(
|
||||||
this.state.room.roomId, "m.room.power_levels", new_power_levels, ""
|
this.state.room.roomId, "m.room.power_levels", newVals.power_levels, ""
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (new_color_scheme) {
|
if (newVals.color_scheme) {
|
||||||
deferreds.push(
|
deferreds.push(
|
||||||
MatrixClientPeg.get().setRoomAccountData(
|
MatrixClientPeg.get().setRoomAccountData(
|
||||||
this.state.room.roomId, "org.matrix.room.color_scheme", new_color_scheme
|
this.state.room.roomId, "org.matrix.room.color_scheme", newVals.color_scheme
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
deferreds.push(
|
||||||
|
MatrixClientPeg.get().setGuestAccess(this.state.room.roomId, {
|
||||||
|
allowRead: newVals.guest_read,
|
||||||
|
allowJoin: newVals.guest_join
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
if (deferreds.length) {
|
if (deferreds.length) {
|
||||||
var self = this;
|
var self = this;
|
||||||
q.all(deferreds).fail(function(err) {
|
q.all(deferreds).fail(function(err) {
|
||||||
|
@ -899,21 +1006,16 @@ module.exports = React.createClass({
|
||||||
uploadingRoomSettings: true,
|
uploadingRoomSettings: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
var new_name = this.refs.header.getRoomName();
|
this.uploadNewState({
|
||||||
var new_topic = this.refs.header.getTopic();
|
name: this.refs.header.getRoomName(),
|
||||||
var new_join_rule = this.refs.room_settings.getJoinRules();
|
topic: this.refs.room_settings.getTopic(),
|
||||||
var new_history_visibility = this.refs.room_settings.getHistoryVisibility();
|
join_rule: this.refs.room_settings.getJoinRules(),
|
||||||
var new_power_levels = this.refs.room_settings.getPowerLevels();
|
history_visibility: this.refs.room_settings.getHistoryVisibility(),
|
||||||
var new_color_scheme = this.refs.room_settings.getColorScheme();
|
power_levels: this.refs.room_settings.getPowerLevels(),
|
||||||
|
guest_join: this.refs.room_settings.canGuestsJoin(),
|
||||||
this.uploadNewState(
|
guest_read: this.refs.room_settings.canGuestsRead(),
|
||||||
new_name,
|
color_scheme: this.refs.room_settings.getColorScheme(),
|
||||||
new_topic,
|
});
|
||||||
new_join_rule,
|
|
||||||
new_history_visibility,
|
|
||||||
new_power_levels,
|
|
||||||
new_color_scheme
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
onCancelClick: function() {
|
onCancelClick: function() {
|
||||||
|
@ -1074,10 +1176,23 @@ module.exports = React.createClass({
|
||||||
if (auxPanelMaxHeight < 50) auxPanelMaxHeight = 50;
|
if (auxPanelMaxHeight < 50) auxPanelMaxHeight = 50;
|
||||||
|
|
||||||
if (this.refs.callView) {
|
if (this.refs.callView) {
|
||||||
// XXX: don't understand why we have to call findDOMNode here in react 0.14 - it should already be a DOM node.
|
var video = this.refs.callView.getVideoView().getRemoteVideoElement();
|
||||||
var video = ReactDOM.findDOMNode(this.refs.callView.refs.video.refs.remote);
|
|
||||||
|
// header + footer + status + give us at least 100px of scrollback at all times.
|
||||||
|
auxPanelMaxHeight = window.innerHeight -
|
||||||
|
(83 + 72 +
|
||||||
|
sdk.getComponent('rooms.MessageComposer').MAX_HEIGHT +
|
||||||
|
100);
|
||||||
|
|
||||||
|
// XXX: this is a bit of a hack and might possibly cause the video to push out the page anyway
|
||||||
|
// but it's better than the video going missing entirely
|
||||||
|
if (auxPanelMaxHeight < 50) auxPanelMaxHeight = 50;
|
||||||
|
|
||||||
video.style.maxHeight = auxPanelMaxHeight + "px";
|
video.style.maxHeight = auxPanelMaxHeight + "px";
|
||||||
|
|
||||||
|
// the above might have made the video panel resize itself, so now
|
||||||
|
// we need to tell the gemini panel to adapt.
|
||||||
|
this.onChildResize();
|
||||||
}
|
}
|
||||||
|
|
||||||
// we need to do this for general auxPanels too
|
// we need to do this for general auxPanels too
|
||||||
|
@ -1117,6 +1232,15 @@ module.exports = React.createClass({
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
onChildResize: function() {
|
||||||
|
// When the video or the message composer resizes, the scroll panel
|
||||||
|
// also changes size. Work around GeminiScrollBar fail by telling it
|
||||||
|
// about it. This also ensures that the scroll offset is updated.
|
||||||
|
if (this.refs.messagePanel) {
|
||||||
|
this.refs.messagePanel.forceUpdate();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
render: function() {
|
render: function() {
|
||||||
var RoomHeader = sdk.getComponent('rooms.RoomHeader');
|
var RoomHeader = sdk.getComponent('rooms.RoomHeader');
|
||||||
var MessageComposer = sdk.getComponent('rooms.MessageComposer');
|
var MessageComposer = sdk.getComponent('rooms.MessageComposer');
|
||||||
|
@ -1307,7 +1431,7 @@ module.exports = React.createClass({
|
||||||
if (canSpeak) {
|
if (canSpeak) {
|
||||||
messageComposer =
|
messageComposer =
|
||||||
<MessageComposer
|
<MessageComposer
|
||||||
room={this.state.room} roomView={this} uploadFile={this.uploadFile}
|
room={this.state.room} onResize={this.onChildResize} uploadFile={this.uploadFile}
|
||||||
callState={this.state.callState} tabComplete={this.tabComplete} />
|
callState={this.state.callState} tabComplete={this.tabComplete} />
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1410,7 +1534,8 @@ module.exports = React.createClass({
|
||||||
} />
|
} />
|
||||||
{ fileDropTarget }
|
{ fileDropTarget }
|
||||||
<div className="mx_RoomView_auxPanel" ref="auxPanel">
|
<div className="mx_RoomView_auxPanel" ref="auxPanel">
|
||||||
<CallView ref="callView" room={this.state.room} ConferenceHandler={this.props.ConferenceHandler}/>
|
<CallView ref="callView" room={this.state.room} ConferenceHandler={this.props.ConferenceHandler}
|
||||||
|
onResize={this.onChildResize} />
|
||||||
{ conferenceCallNotification }
|
{ conferenceCallNotification }
|
||||||
{ aux }
|
{ aux }
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -112,6 +112,14 @@ module.exports = React.createClass({
|
||||||
this.checkFillState();
|
this.checkFillState();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
componentWillUnmount: function() {
|
||||||
|
// set a boolean to say we've been unmounted, which any pending
|
||||||
|
// promises can use to throw away their results.
|
||||||
|
//
|
||||||
|
// (We could use isMounted(), but facebook have deprecated that.)
|
||||||
|
this.unmounted = true;
|
||||||
|
},
|
||||||
|
|
||||||
onScroll: function(ev) {
|
onScroll: function(ev) {
|
||||||
var sn = this._getScrollNode();
|
var sn = this._getScrollNode();
|
||||||
debuglog("Scroll event: offset now:", sn.scrollTop, "recentEventScroll:", this.recentEventScroll);
|
debuglog("Scroll event: offset now:", sn.scrollTop, "recentEventScroll:", this.recentEventScroll);
|
||||||
|
@ -158,6 +166,10 @@ module.exports = React.createClass({
|
||||||
|
|
||||||
// check the scroll state and send out backfill requests if necessary.
|
// check the scroll state and send out backfill requests if necessary.
|
||||||
checkFillState: function() {
|
checkFillState: function() {
|
||||||
|
if (this.unmounted) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var sn = this._getScrollNode();
|
var sn = this._getScrollNode();
|
||||||
|
|
||||||
// if there is less than a screenful of messages above or below the
|
// if there is less than a screenful of messages above or below the
|
||||||
|
@ -346,6 +358,12 @@ module.exports = React.createClass({
|
||||||
* message panel.
|
* message panel.
|
||||||
*/
|
*/
|
||||||
_getScrollNode: function() {
|
_getScrollNode: function() {
|
||||||
|
if (this.unmounted) {
|
||||||
|
// this shouldn't happen, but when it does, turn the NPE into
|
||||||
|
// something more meaningful.
|
||||||
|
throw new Error("ScrollPanel._getScrollNode called when unmounted");
|
||||||
|
}
|
||||||
|
|
||||||
var panel = ReactDOM.findDOMNode(this.refs.geminiPanel);
|
var panel = ReactDOM.findDOMNode(this.refs.geminiPanel);
|
||||||
|
|
||||||
// If the gemini scrollbar is doing its thing, this will be a div within
|
// If the gemini scrollbar is doing its thing, this will be a div within
|
||||||
|
|
|
@ -135,6 +135,12 @@ module.exports = React.createClass({
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
onUpgradeClicked: function() {
|
||||||
|
dis.dispatch({
|
||||||
|
action: "start_upgrade_registration"
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
onLogoutPromptCancel: function() {
|
onLogoutPromptCancel: function() {
|
||||||
this.logoutModal.closeDialog();
|
this.logoutModal.closeDialog();
|
||||||
},
|
},
|
||||||
|
@ -164,6 +170,28 @@ module.exports = React.createClass({
|
||||||
this.state.avatarUrl ? MatrixClientPeg.get().mxcUrlToHttp(this.state.avatarUrl) : null
|
this.state.avatarUrl ? MatrixClientPeg.get().mxcUrlToHttp(this.state.avatarUrl) : null
|
||||||
);
|
);
|
||||||
|
|
||||||
|
var accountJsx;
|
||||||
|
|
||||||
|
if (MatrixClientPeg.get().isGuest()) {
|
||||||
|
accountJsx = (
|
||||||
|
<div className="mx_UserSettings_button" onClick={this.onUpgradeClicked}>
|
||||||
|
Upgrade (It's free!)
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
accountJsx = (
|
||||||
|
<ChangePassword
|
||||||
|
className="mx_UserSettings_accountTable"
|
||||||
|
rowClassName="mx_UserSettings_profileTableRow"
|
||||||
|
rowLabelClassName="mx_UserSettings_profileLabelCell"
|
||||||
|
rowInputClassName="mx_UserSettings_profileInputCell"
|
||||||
|
buttonClassName="mx_UserSettings_button"
|
||||||
|
onError={this.onPasswordChangeError}
|
||||||
|
onFinished={this.onPasswordChanged} />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mx_UserSettings">
|
<div className="mx_UserSettings">
|
||||||
<RoomHeader simpleHeader="Settings" />
|
<RoomHeader simpleHeader="Settings" />
|
||||||
|
@ -213,14 +241,7 @@ module.exports = React.createClass({
|
||||||
<h2>Account</h2>
|
<h2>Account</h2>
|
||||||
|
|
||||||
<div className="mx_UserSettings_section">
|
<div className="mx_UserSettings_section">
|
||||||
<ChangePassword
|
{accountJsx}
|
||||||
className="mx_UserSettings_accountTable"
|
|
||||||
rowClassName="mx_UserSettings_profileTableRow"
|
|
||||||
rowLabelClassName="mx_UserSettings_profileLabelCell"
|
|
||||||
rowInputClassName="mx_UserSettings_profileInputCell"
|
|
||||||
buttonClassName="mx_UserSettings_button"
|
|
||||||
onError={this.onPasswordChangeError}
|
|
||||||
onFinished={this.onPasswordChanged} />
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mx_UserSettings_logout">
|
<div className="mx_UserSettings_logout">
|
||||||
|
|
|
@ -0,0 +1,199 @@
|
||||||
|
/*
|
||||||
|
Copyright 2015, 2016 OpenMarket Ltd
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var React = require('react');
|
||||||
|
var sdk = require('../../../index');
|
||||||
|
var Modal = require("../../../Modal");
|
||||||
|
var MatrixClientPeg = require('../../../MatrixClientPeg');
|
||||||
|
|
||||||
|
var PasswordReset = require("../../../PasswordReset");
|
||||||
|
|
||||||
|
module.exports = React.createClass({
|
||||||
|
displayName: 'ForgotPassword',
|
||||||
|
|
||||||
|
propTypes: {
|
||||||
|
homeserverUrl: React.PropTypes.string,
|
||||||
|
identityServerUrl: React.PropTypes.string,
|
||||||
|
onComplete: React.PropTypes.func.isRequired
|
||||||
|
},
|
||||||
|
|
||||||
|
getInitialState: function() {
|
||||||
|
return {
|
||||||
|
enteredHomeserverUrl: this.props.homeserverUrl,
|
||||||
|
enteredIdentityServerUrl: this.props.identityServerUrl,
|
||||||
|
progress: null
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
submitPasswordReset: function(hsUrl, identityUrl, email, password) {
|
||||||
|
this.setState({
|
||||||
|
progress: "sending_email"
|
||||||
|
});
|
||||||
|
this.reset = new PasswordReset(hsUrl, identityUrl);
|
||||||
|
this.reset.resetPassword(email, password).done(() => {
|
||||||
|
this.setState({
|
||||||
|
progress: "sent_email"
|
||||||
|
});
|
||||||
|
}, (err) => {
|
||||||
|
this.showErrorDialog("Failed to send email: " + err.message);
|
||||||
|
this.setState({
|
||||||
|
progress: null
|
||||||
|
});
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
onVerify: function(ev) {
|
||||||
|
ev.preventDefault();
|
||||||
|
if (!this.reset) {
|
||||||
|
console.error("onVerify called before submitPasswordReset!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.reset.checkEmailLinkClicked().done((res) => {
|
||||||
|
this.setState({ progress: "complete" });
|
||||||
|
}, (err) => {
|
||||||
|
this.showErrorDialog(err.message);
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
onSubmitForm: function(ev) {
|
||||||
|
ev.preventDefault();
|
||||||
|
|
||||||
|
if (!this.state.email) {
|
||||||
|
this.showErrorDialog("The email address linked to your account must be entered.");
|
||||||
|
}
|
||||||
|
else if (!this.state.password || !this.state.password2) {
|
||||||
|
this.showErrorDialog("A new password must be entered.");
|
||||||
|
}
|
||||||
|
else if (this.state.password !== this.state.password2) {
|
||||||
|
this.showErrorDialog("New passwords must match each other.");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.submitPasswordReset(
|
||||||
|
this.state.enteredHomeserverUrl, this.state.enteredIdentityServerUrl,
|
||||||
|
this.state.email, this.state.password
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
onInputChanged: function(stateKey, ev) {
|
||||||
|
this.setState({
|
||||||
|
[stateKey]: ev.target.value
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
onHsUrlChanged: function(newHsUrl) {
|
||||||
|
this.setState({
|
||||||
|
enteredHomeserverUrl: newHsUrl
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
onIsUrlChanged: function(newIsUrl) {
|
||||||
|
this.setState({
|
||||||
|
enteredIdentityServerUrl: newIsUrl
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
showErrorDialog: function(body, title) {
|
||||||
|
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||||
|
Modal.createDialog(ErrorDialog, {
|
||||||
|
title: title,
|
||||||
|
description: body
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
render: function() {
|
||||||
|
var LoginHeader = sdk.getComponent("login.LoginHeader");
|
||||||
|
var LoginFooter = sdk.getComponent("login.LoginFooter");
|
||||||
|
var ServerConfig = sdk.getComponent("login.ServerConfig");
|
||||||
|
var Spinner = sdk.getComponent("elements.Spinner");
|
||||||
|
|
||||||
|
var resetPasswordJsx;
|
||||||
|
|
||||||
|
if (this.state.progress === "sending_email") {
|
||||||
|
resetPasswordJsx = <Spinner />
|
||||||
|
}
|
||||||
|
else if (this.state.progress === "sent_email") {
|
||||||
|
resetPasswordJsx = (
|
||||||
|
<div>
|
||||||
|
An email has been sent to {this.state.email}. Once you've followed
|
||||||
|
the link it contains, click below.
|
||||||
|
<br />
|
||||||
|
<input className="mx_Login_submit" type="button" onClick={this.onVerify}
|
||||||
|
value="I have verified my email address" />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else if (this.state.progress === "complete") {
|
||||||
|
resetPasswordJsx = (
|
||||||
|
<div>
|
||||||
|
<p>Your password has been reset.</p>
|
||||||
|
<p>You have been logged out of all devices and will no longer receive push notifications.
|
||||||
|
To re-enable notifications, re-log in on each device.</p>
|
||||||
|
<input className="mx_Login_submit" type="button" onClick={this.props.onComplete}
|
||||||
|
value="Return to login screen" />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
resetPasswordJsx = (
|
||||||
|
<div>
|
||||||
|
To reset your password, enter the email address linked to your account:
|
||||||
|
<br />
|
||||||
|
<div>
|
||||||
|
<form onSubmit={this.onSubmitForm}>
|
||||||
|
<input className="mx_Login_field" ref="user" type="text"
|
||||||
|
value={this.state.email}
|
||||||
|
onChange={this.onInputChanged.bind(this, "email")}
|
||||||
|
placeholder="Email address" autoFocus />
|
||||||
|
<br />
|
||||||
|
<input className="mx_Login_field" ref="pass" type="password"
|
||||||
|
value={this.state.password}
|
||||||
|
onChange={this.onInputChanged.bind(this, "password")}
|
||||||
|
placeholder="New password" />
|
||||||
|
<br />
|
||||||
|
<input className="mx_Login_field" ref="pass" type="password"
|
||||||
|
value={this.state.password2}
|
||||||
|
onChange={this.onInputChanged.bind(this, "password2")}
|
||||||
|
placeholder="Confirm your new password" />
|
||||||
|
<br />
|
||||||
|
<input className="mx_Login_submit" type="submit" value="Send Reset Email" />
|
||||||
|
</form>
|
||||||
|
<ServerConfig ref="serverConfig"
|
||||||
|
withToggleButton={true}
|
||||||
|
defaultHsUrl={this.props.homeserverUrl}
|
||||||
|
defaultIsUrl={this.props.identityServerUrl}
|
||||||
|
onHsUrlChanged={this.onHsUrlChanged}
|
||||||
|
onIsUrlChanged={this.onIsUrlChanged}
|
||||||
|
delayTimeMs={0}/>
|
||||||
|
<LoginFooter />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="mx_Login">
|
||||||
|
<div className="mx_Login_box">
|
||||||
|
<LoginHeader />
|
||||||
|
{resetPasswordJsx}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
|
@ -33,7 +33,9 @@ module.exports = React.createClass({displayName: 'Login',
|
||||||
homeserverUrl: React.PropTypes.string,
|
homeserverUrl: React.PropTypes.string,
|
||||||
identityServerUrl: React.PropTypes.string,
|
identityServerUrl: React.PropTypes.string,
|
||||||
// login shouldn't know or care how registration is done.
|
// login shouldn't know or care how registration is done.
|
||||||
onRegisterClick: React.PropTypes.func.isRequired
|
onRegisterClick: React.PropTypes.func.isRequired,
|
||||||
|
// login shouldn't care how password recovery is done.
|
||||||
|
onForgotPasswordClick: React.PropTypes.func
|
||||||
},
|
},
|
||||||
|
|
||||||
getDefaultProps: function() {
|
getDefaultProps: function() {
|
||||||
|
@ -138,7 +140,9 @@ module.exports = React.createClass({displayName: 'Login',
|
||||||
switch (step) {
|
switch (step) {
|
||||||
case 'm.login.password':
|
case 'm.login.password':
|
||||||
return (
|
return (
|
||||||
<PasswordLogin onSubmit={this.onPasswordLogin} />
|
<PasswordLogin
|
||||||
|
onSubmit={this.onPasswordLogin}
|
||||||
|
onForgotPasswordClick={this.props.onForgotPasswordClick} />
|
||||||
);
|
);
|
||||||
case 'm.login.cas':
|
case 'm.login.cas':
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -19,7 +19,6 @@ limitations under the License.
|
||||||
var React = require('react');
|
var React = require('react');
|
||||||
|
|
||||||
var sdk = require('../../../index');
|
var sdk = require('../../../index');
|
||||||
var MatrixClientPeg = require('../../../MatrixClientPeg');
|
|
||||||
var dis = require('../../../dispatcher');
|
var dis = require('../../../dispatcher');
|
||||||
var Signup = require("../../../Signup");
|
var Signup = require("../../../Signup");
|
||||||
var ServerConfig = require("../../views/login/ServerConfig");
|
var ServerConfig = require("../../views/login/ServerConfig");
|
||||||
|
@ -40,6 +39,9 @@ module.exports = React.createClass({
|
||||||
hsUrl: React.PropTypes.string,
|
hsUrl: React.PropTypes.string,
|
||||||
isUrl: React.PropTypes.string,
|
isUrl: React.PropTypes.string,
|
||||||
email: React.PropTypes.string,
|
email: React.PropTypes.string,
|
||||||
|
username: React.PropTypes.string,
|
||||||
|
guestAccessToken: React.PropTypes.string,
|
||||||
|
disableUsernameChanges: React.PropTypes.bool,
|
||||||
// registration shouldn't know or care how login is done.
|
// registration shouldn't know or care how login is done.
|
||||||
onLoginClick: React.PropTypes.func.isRequired
|
onLoginClick: React.PropTypes.func.isRequired
|
||||||
},
|
},
|
||||||
|
@ -63,6 +65,7 @@ module.exports = React.createClass({
|
||||||
this.registerLogic.setSessionId(this.props.sessionId);
|
this.registerLogic.setSessionId(this.props.sessionId);
|
||||||
this.registerLogic.setRegistrationUrl(this.props.registrationUrl);
|
this.registerLogic.setRegistrationUrl(this.props.registrationUrl);
|
||||||
this.registerLogic.setIdSid(this.props.idSid);
|
this.registerLogic.setIdSid(this.props.idSid);
|
||||||
|
this.registerLogic.setGuestAccessToken(this.props.guestAccessToken);
|
||||||
this.registerLogic.recheckState();
|
this.registerLogic.recheckState();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -186,7 +189,9 @@ module.exports = React.createClass({
|
||||||
registerStep = (
|
registerStep = (
|
||||||
<RegistrationForm
|
<RegistrationForm
|
||||||
showEmail={true}
|
showEmail={true}
|
||||||
|
defaultUsername={this.props.username}
|
||||||
defaultEmail={this.props.email}
|
defaultEmail={this.props.email}
|
||||||
|
disableUsernameChanges={this.props.disableUsernameChanges}
|
||||||
minPasswordLength={MIN_PASSWORD_LENGTH}
|
minPasswordLength={MIN_PASSWORD_LENGTH}
|
||||||
onError={this.onFormValidationFailed}
|
onError={this.onFormValidationFailed}
|
||||||
onRegisterClick={this.onFormSubmit} />
|
onRegisterClick={this.onFormSubmit} />
|
||||||
|
|
|
@ -22,7 +22,8 @@ var ReactDOM = require('react-dom');
|
||||||
*/
|
*/
|
||||||
module.exports = React.createClass({displayName: 'PasswordLogin',
|
module.exports = React.createClass({displayName: 'PasswordLogin',
|
||||||
propTypes: {
|
propTypes: {
|
||||||
onSubmit: React.PropTypes.func.isRequired // fn(username, password)
|
onSubmit: React.PropTypes.func.isRequired, // fn(username, password)
|
||||||
|
onForgotPasswordClick: React.PropTypes.func // fn()
|
||||||
},
|
},
|
||||||
|
|
||||||
getInitialState: function() {
|
getInitialState: function() {
|
||||||
|
@ -46,6 +47,16 @@ module.exports = React.createClass({displayName: 'PasswordLogin',
|
||||||
},
|
},
|
||||||
|
|
||||||
render: function() {
|
render: function() {
|
||||||
|
var forgotPasswordJsx;
|
||||||
|
|
||||||
|
if (this.props.onForgotPasswordClick) {
|
||||||
|
forgotPasswordJsx = (
|
||||||
|
<a className="mx_Login_forgot" onClick={this.props.onForgotPasswordClick} href="#">
|
||||||
|
Forgot your password?
|
||||||
|
</a>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<form onSubmit={this.onSubmitForm}>
|
<form onSubmit={this.onSubmitForm}>
|
||||||
|
@ -57,6 +68,7 @@ module.exports = React.createClass({displayName: 'PasswordLogin',
|
||||||
value={this.state.password} onChange={this.onPasswordChanged}
|
value={this.state.password} onChange={this.onPasswordChanged}
|
||||||
placeholder="Password" />
|
placeholder="Password" />
|
||||||
<br />
|
<br />
|
||||||
|
{forgotPasswordJsx}
|
||||||
<input className="mx_Login_submit" type="submit" value="Log in" />
|
<input className="mx_Login_submit" type="submit" value="Log in" />
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -30,6 +30,7 @@ module.exports = React.createClass({
|
||||||
defaultUsername: React.PropTypes.string,
|
defaultUsername: React.PropTypes.string,
|
||||||
showEmail: React.PropTypes.bool,
|
showEmail: React.PropTypes.bool,
|
||||||
minPasswordLength: React.PropTypes.number,
|
minPasswordLength: React.PropTypes.number,
|
||||||
|
disableUsernameChanges: React.PropTypes.bool,
|
||||||
onError: React.PropTypes.func,
|
onError: React.PropTypes.func,
|
||||||
onRegisterClick: React.PropTypes.func // onRegisterClick(Object) => ?Promise
|
onRegisterClick: React.PropTypes.func // onRegisterClick(Object) => ?Promise
|
||||||
},
|
},
|
||||||
|
@ -109,7 +110,8 @@ module.exports = React.createClass({
|
||||||
{emailSection}
|
{emailSection}
|
||||||
<br />
|
<br />
|
||||||
<input className="mx_Login_field" type="text" ref="username"
|
<input className="mx_Login_field" type="text" ref="username"
|
||||||
placeholder="User name" defaultValue={this.state.username} />
|
placeholder="User name" defaultValue={this.state.username}
|
||||||
|
disabled={this.props.disableUsernameChanges} />
|
||||||
<br />
|
<br />
|
||||||
<input className="mx_Login_field" type="password" ref="password"
|
<input className="mx_Login_field" type="password" ref="password"
|
||||||
placeholder="Password" defaultValue={this.state.password} />
|
placeholder="Password" defaultValue={this.state.password} />
|
||||||
|
|
|
@ -65,8 +65,17 @@ function mdownToHtml(mdown) {
|
||||||
module.exports = React.createClass({
|
module.exports = React.createClass({
|
||||||
displayName: 'MessageComposer',
|
displayName: 'MessageComposer',
|
||||||
|
|
||||||
|
statics: {
|
||||||
|
// the height we limit the composer to
|
||||||
|
MAX_HEIGHT: 100,
|
||||||
|
},
|
||||||
|
|
||||||
propTypes: {
|
propTypes: {
|
||||||
tabComplete: React.PropTypes.any
|
tabComplete: React.PropTypes.any,
|
||||||
|
|
||||||
|
// a callback which is called when the height of the composer is
|
||||||
|
// changed due to a change in content.
|
||||||
|
onResize: React.PropTypes.func,
|
||||||
},
|
},
|
||||||
|
|
||||||
componentWillMount: function() {
|
componentWillMount: function() {
|
||||||
|
@ -237,13 +246,15 @@ module.exports = React.createClass({
|
||||||
// scrollHeight is at least equal to clientHeight, so we have to
|
// scrollHeight is at least equal to clientHeight, so we have to
|
||||||
// temporarily crimp clientHeight to 0 to get an accurate scrollHeight value
|
// temporarily crimp clientHeight to 0 to get an accurate scrollHeight value
|
||||||
this.refs.textarea.style.height = "0px";
|
this.refs.textarea.style.height = "0px";
|
||||||
var newHeight = this.refs.textarea.scrollHeight < 100 ? this.refs.textarea.scrollHeight : 100;
|
var newHeight = Math.min(this.refs.textarea.scrollHeight,
|
||||||
|
this.constructor.MAX_HEIGHT);
|
||||||
this.refs.textarea.style.height = Math.ceil(newHeight) + "px";
|
this.refs.textarea.style.height = Math.ceil(newHeight) + "px";
|
||||||
if (this.props.roomView) {
|
|
||||||
// kick gemini-scrollbar to re-layout
|
|
||||||
this.props.roomView.forceUpdate();
|
|
||||||
}
|
|
||||||
this.oldScrollHeight = this.refs.textarea.scrollHeight;
|
this.oldScrollHeight = this.refs.textarea.scrollHeight;
|
||||||
|
|
||||||
|
if (this.props.onResize) {
|
||||||
|
// kick gemini-scrollbar to re-layout
|
||||||
|
this.props.onResize();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
onKeyUp: function(ev) {
|
onKeyUp: function(ev) {
|
||||||
|
|
|
@ -77,6 +77,14 @@ module.exports = React.createClass({
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
canGuestsJoin: function() {
|
||||||
|
return this.refs.guests_join.checked;
|
||||||
|
},
|
||||||
|
|
||||||
|
canGuestsRead: function() {
|
||||||
|
return this.refs.guests_read.checked;
|
||||||
|
},
|
||||||
|
|
||||||
getTopic: function() {
|
getTopic: function() {
|
||||||
return this.refs.topic.value;
|
return this.refs.topic.value;
|
||||||
},
|
},
|
||||||
|
@ -162,6 +170,10 @@ module.exports = React.createClass({
|
||||||
if (history_visibility) history_visibility = history_visibility.getContent().history_visibility;
|
if (history_visibility) history_visibility = history_visibility.getContent().history_visibility;
|
||||||
|
|
||||||
var power_levels = this.props.room.currentState.getStateEvents('m.room.power_levels', '');
|
var power_levels = this.props.room.currentState.getStateEvents('m.room.power_levels', '');
|
||||||
|
var guest_access = this.props.room.currentState.getStateEvents('m.room.guest_access', '');
|
||||||
|
if (guest_access) {
|
||||||
|
guest_access = guest_access.getContent().guest_access;
|
||||||
|
}
|
||||||
|
|
||||||
var events_levels = power_levels.events || {};
|
var events_levels = power_levels.events || {};
|
||||||
|
|
||||||
|
@ -361,8 +373,15 @@ module.exports = React.createClass({
|
||||||
<div className="mx_RoomSettings">
|
<div className="mx_RoomSettings">
|
||||||
<label><input type="checkbox" ref="is_private" defaultChecked={join_rule != "public"}/> Make this room private</label> <br/>
|
<label><input type="checkbox" ref="is_private" defaultChecked={join_rule != "public"}/> Make this room private</label> <br/>
|
||||||
<label><input type="checkbox" ref="share_history" defaultChecked={history_visibility == "shared"}/> Share message history with new users</label> <br/>
|
<label><input type="checkbox" ref="share_history" defaultChecked={history_visibility == "shared"}/> Share message history with new users</label> <br/>
|
||||||
<label><input type="checkbox" ref="guest_access" disabled={join_rule != "public"} defaultChecked={history_visibility == "world_readable"}/> Allow guest access</label> <br/>
|
<label>
|
||||||
<label className="mx_RoomSettings_encrypt"><input type="checkbox" /> Encrypt room</label>
|
<input type="checkbox" ref="guests_read" defaultChecked={history_visibility === "world_readable"}/>
|
||||||
|
Allow guests to read messages in this room
|
||||||
|
</label> <br/>
|
||||||
|
<label>
|
||||||
|
<input type="checkbox" ref="guests_join" defaultChecked={guest_access === "can_join"}/>
|
||||||
|
Allow guests to join this room
|
||||||
|
</label> <br/>
|
||||||
|
<label className="mx_RoomSettings_encrypt"><input type="checkbox" /> Encrypt room</label> <br/>
|
||||||
|
|
||||||
{ room_colors_section }
|
{ room_colors_section }
|
||||||
|
|
||||||
|
|
|
@ -33,6 +33,12 @@ var MatrixClientPeg = require("../../../MatrixClientPeg");
|
||||||
module.exports = React.createClass({
|
module.exports = React.createClass({
|
||||||
displayName: 'CallView',
|
displayName: 'CallView',
|
||||||
|
|
||||||
|
propTypes: {
|
||||||
|
// a callback which is called when the video within the callview
|
||||||
|
// due to a change in video metadata
|
||||||
|
onResize: React.PropTypes.func,
|
||||||
|
},
|
||||||
|
|
||||||
componentDidMount: function() {
|
componentDidMount: function() {
|
||||||
this.dispatcherRef = dis.register(this.onAction);
|
this.dispatcherRef = dis.register(this.onAction);
|
||||||
if (this.props.room) {
|
if (this.props.room) {
|
||||||
|
@ -97,7 +103,7 @@ module.exports = React.createClass({
|
||||||
render: function(){
|
render: function(){
|
||||||
var VideoView = sdk.getComponent('voip.VideoView');
|
var VideoView = sdk.getComponent('voip.VideoView');
|
||||||
return (
|
return (
|
||||||
<VideoView ref="video" onClick={ this.props.onClick }/>
|
<VideoView ref="video" onClick={ this.props.onClick } onResize={ this.props.onResize }/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -21,9 +21,29 @@ var React = require('react');
|
||||||
module.exports = React.createClass({
|
module.exports = React.createClass({
|
||||||
displayName: 'VideoFeed',
|
displayName: 'VideoFeed',
|
||||||
|
|
||||||
|
propTypes: {
|
||||||
|
// a callback which is called when the video element is resized
|
||||||
|
// due to a change in video metadata
|
||||||
|
onResize: React.PropTypes.func,
|
||||||
|
},
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this.refs.vid.addEventListener('resize', this.onResize);
|
||||||
|
},
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
this.refs.vid.removeEventListener('resize', this.onResize);
|
||||||
|
},
|
||||||
|
|
||||||
|
onResize: function(e) {
|
||||||
|
if(this.props.onResize) {
|
||||||
|
this.props.onResize(e);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
render: function() {
|
render: function() {
|
||||||
return (
|
return (
|
||||||
<video>
|
<video ref="vid">
|
||||||
</video>
|
</video>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
|
@ -85,7 +85,7 @@ module.exports = React.createClass({
|
||||||
return (
|
return (
|
||||||
<div className="mx_VideoView" ref={this.setContainer} onClick={ this.props.onClick }>
|
<div className="mx_VideoView" ref={this.setContainer} onClick={ this.props.onClick }>
|
||||||
<div className="mx_VideoView_remoteVideoFeed">
|
<div className="mx_VideoView_remoteVideoFeed">
|
||||||
<VideoFeed ref="remote"/>
|
<VideoFeed ref="remote" onResize={this.props.onResize}/>
|
||||||
<audio ref="remoteAudio"/>
|
<audio ref="remoteAudio"/>
|
||||||
</div>
|
</div>
|
||||||
<div className="mx_VideoView_localVideoFeed">
|
<div className="mx_VideoView_localVideoFeed">
|
||||||
|
|
Loading…
Reference in New Issue