Merge branch 'develop' of github.com:matrix-org/matrix-react-sdk into feature-rte
This commit is contained in:
commit
bf8e56e04c
24 changed files with 543 additions and 178 deletions
40
CHANGELOG.md
40
CHANGELOG.md
|
@ -1,3 +1,43 @@
|
||||||
|
Changes in [0.6.3](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v0.6.3) (2016-06-03)
|
||||||
|
===================================================================================================
|
||||||
|
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v0.6.2...v0.6.3)
|
||||||
|
|
||||||
|
* Change invite text field wording
|
||||||
|
* Fix bug with new email invite UX where the invite could get wedged
|
||||||
|
* Label app versions sensibly in UserSettings
|
||||||
|
|
||||||
|
Changes in [0.6.2](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v0.6.2) (2016-06-02)
|
||||||
|
===================================================================================================
|
||||||
|
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v0.6.1...v0.6.2)
|
||||||
|
|
||||||
|
* Correctly bump dep on matrix-js-sdk 0.5.4
|
||||||
|
|
||||||
|
Changes in [0.6.1](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v0.6.1) (2016-06-02)
|
||||||
|
===================================================================================================
|
||||||
|
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v0.6.0...v0.6.1)
|
||||||
|
|
||||||
|
* Fix focusing race in new UX for 3pid invites
|
||||||
|
* Fix jenkins.sh
|
||||||
|
|
||||||
|
Changes in [0.6.0](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v0.6.0) (2016-06-02)
|
||||||
|
===================================================================================================
|
||||||
|
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v0.5.2...v0.6.0)
|
||||||
|
|
||||||
|
* implement new UX for 3pid invites
|
||||||
|
[\#297](https://github.com/matrix-org/matrix-react-sdk/pull/297)
|
||||||
|
* multiple URL preview support
|
||||||
|
[\#290](https://github.com/matrix-org/matrix-react-sdk/pull/290)
|
||||||
|
* Add a fallback home server to log into
|
||||||
|
[\#293](https://github.com/matrix-org/matrix-react-sdk/pull/293)
|
||||||
|
* Hopefully fix memory leak with velocity
|
||||||
|
[\#291](https://github.com/matrix-org/matrix-react-sdk/pull/291)
|
||||||
|
* Support for enabling email notifications
|
||||||
|
[\#289](https://github.com/matrix-org/matrix-react-sdk/pull/289)
|
||||||
|
* Correct Readme instructions how to customize the UI
|
||||||
|
[\#286](https://github.com/matrix-org/matrix-react-sdk/pull/286)
|
||||||
|
* Avoid rerendering during Room unmount
|
||||||
|
[\#285](https://github.com/matrix-org/matrix-react-sdk/pull/285)
|
||||||
|
|
||||||
Changes in [0.5.2](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v0.5.2) (2016-04-22)
|
Changes in [0.5.2](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v0.5.2) (2016-04-22)
|
||||||
===================================================================================================
|
===================================================================================================
|
||||||
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v0.5.1...v0.5.2)
|
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v0.5.1...v0.5.2)
|
||||||
|
|
|
@ -8,9 +8,6 @@ nvm use 4
|
||||||
|
|
||||||
set -x
|
set -x
|
||||||
|
|
||||||
# install the version of js-sdk provided to us by jenkins
|
|
||||||
npm install ./node_modules/matrix-js-sdk-*.tgz
|
|
||||||
|
|
||||||
# install the other dependencies
|
# install the other dependencies
|
||||||
npm install
|
npm install
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "matrix-react-sdk",
|
"name": "matrix-react-sdk",
|
||||||
"version": "0.5.2",
|
"version": "0.6.3",
|
||||||
"description": "SDK for matrix.org using React",
|
"description": "SDK for matrix.org using React",
|
||||||
"author": "matrix.org",
|
"author": "matrix.org",
|
||||||
"repository": {
|
"repository": {
|
||||||
|
@ -32,15 +32,14 @@
|
||||||
"highlight.js": "^8.9.1",
|
"highlight.js": "^8.9.1",
|
||||||
"linkifyjs": "^2.0.0-beta.4",
|
"linkifyjs": "^2.0.0-beta.4",
|
||||||
"marked": "^0.3.5",
|
"marked": "^0.3.5",
|
||||||
"matrix-js-sdk": "^0.5.2",
|
"matrix-js-sdk": "matrix-org/matrix-js-sdk#develop",
|
||||||
"optimist": "^0.6.1",
|
"optimist": "^0.6.1",
|
||||||
"q": "^1.4.1",
|
"q": "^1.4.1",
|
||||||
"react": "^15.0.1",
|
"react": "^15.0.1",
|
||||||
"react-dom": "^15.0.1",
|
"react-dom": "^15.0.1",
|
||||||
"react-gemini-scrollbar": "matrix-org/react-gemini-scrollbar#c3d942e",
|
"react-gemini-scrollbar": "matrix-org/react-gemini-scrollbar#c3d942e",
|
||||||
"sanitize-html": "^1.11.1",
|
"sanitize-html": "^1.11.1",
|
||||||
"velocity-animate": "^1.2.3",
|
"velocity-vector": "vector-im/velocity#059e3b2"
|
||||||
"velocity-ui-pack": "^1.2.2"
|
|
||||||
},
|
},
|
||||||
"//babelversion": [
|
"//babelversion": [
|
||||||
"brief experiments with babel6 seems to show that it generates source ",
|
"brief experiments with babel6 seems to show that it generates source ",
|
||||||
|
|
|
@ -100,7 +100,7 @@ module.exports = {
|
||||||
return this.getEmailPusher(pushers, address) !== undefined;
|
return this.getEmailPusher(pushers, address) !== undefined;
|
||||||
},
|
},
|
||||||
|
|
||||||
addEmailPusher: function(address) {
|
addEmailPusher: function(address, data) {
|
||||||
return MatrixClientPeg.get().setPusher({
|
return MatrixClientPeg.get().setPusher({
|
||||||
kind: 'email',
|
kind: 'email',
|
||||||
app_id: "m.email",
|
app_id: "m.email",
|
||||||
|
@ -108,7 +108,7 @@ module.exports = {
|
||||||
app_display_name: 'Email Notifications',
|
app_display_name: 'Email Notifications',
|
||||||
device_display_name: address,
|
device_display_name: address,
|
||||||
lang: navigator.language,
|
lang: navigator.language,
|
||||||
data: {},
|
data: data,
|
||||||
append: true, // We always append for email pushers since we don't want to stop other accounts notifying to the same email address
|
append: true, // We always append for email pushers since we don't want to stop other accounts notifying to the same email address
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
var React = require('react');
|
var React = require('react');
|
||||||
var ReactDom = require('react-dom');
|
var ReactDom = require('react-dom');
|
||||||
var Velocity = require('velocity-animate');
|
var Velocity = require('velocity-vector');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The Velociraptor contains components and animates transitions with velocity.
|
* The Velociraptor contains components and animates transitions with velocity.
|
||||||
|
@ -117,7 +117,8 @@ module.exports = React.createClass({
|
||||||
// and the FAQ entry, "Preventing memory leaks when
|
// and the FAQ entry, "Preventing memory leaks when
|
||||||
// creating/destroying large numbers of elements"
|
// creating/destroying large numbers of elements"
|
||||||
// (https://github.com/julianshapiro/velocity/issues/47)
|
// (https://github.com/julianshapiro/velocity/issues/47)
|
||||||
Velocity.Utilities.removeData(this.nodes[k]);
|
var domNode = ReactDom.findDOMNode(this.nodes[k]);
|
||||||
|
Velocity.Utilities.removeData(domNode);
|
||||||
}
|
}
|
||||||
this.nodes[k] = node;
|
this.nodes[k] = node;
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
var Velocity = require('velocity-animate');
|
var Velocity = require('velocity-vector');
|
||||||
|
|
||||||
// courtesy of https://github.com/julianshapiro/velocity/issues/283
|
// courtesy of https://github.com/julianshapiro/velocity/issues/283
|
||||||
// We only use easeOutBounce (easeInBounce is just sort of nonsensical)
|
// We only use easeOutBounce (easeInBounce is just sort of nonsensical)
|
||||||
|
|
|
@ -79,6 +79,7 @@ module.exports.components['views.rooms.EntityTile'] = require('./components/view
|
||||||
module.exports.components['views.rooms.EventTile'] = require('./components/views/rooms/EventTile');
|
module.exports.components['views.rooms.EventTile'] = require('./components/views/rooms/EventTile');
|
||||||
module.exports.components['views.rooms.InviteMemberList'] = require('./components/views/rooms/InviteMemberList');
|
module.exports.components['views.rooms.InviteMemberList'] = require('./components/views/rooms/InviteMemberList');
|
||||||
module.exports.components['views.rooms.LinkPreviewWidget'] = require('./components/views/rooms/LinkPreviewWidget');
|
module.exports.components['views.rooms.LinkPreviewWidget'] = require('./components/views/rooms/LinkPreviewWidget');
|
||||||
|
module.exports.components['views.rooms.MemberDeviceInfo'] = require('./components/views/rooms/MemberDeviceInfo');
|
||||||
module.exports.components['views.rooms.MemberInfo'] = require('./components/views/rooms/MemberInfo');
|
module.exports.components['views.rooms.MemberInfo'] = require('./components/views/rooms/MemberInfo');
|
||||||
module.exports.components['views.rooms.MemberList'] = require('./components/views/rooms/MemberList');
|
module.exports.components['views.rooms.MemberList'] = require('./components/views/rooms/MemberList');
|
||||||
module.exports.components['views.rooms.MemberTile'] = require('./components/views/rooms/MemberTile');
|
module.exports.components['views.rooms.MemberTile'] = require('./components/views/rooms/MemberTile');
|
||||||
|
|
|
@ -37,11 +37,13 @@ var MatrixTools = require('../../MatrixTools');
|
||||||
var linkifyMatrix = require("../../linkify-matrix");
|
var linkifyMatrix = require("../../linkify-matrix");
|
||||||
var KeyCode = require('../../KeyCode');
|
var KeyCode = require('../../KeyCode');
|
||||||
|
|
||||||
|
var createRoom = require("../../createRoom");
|
||||||
|
|
||||||
module.exports = React.createClass({
|
module.exports = React.createClass({
|
||||||
displayName: 'MatrixChat',
|
displayName: 'MatrixChat',
|
||||||
|
|
||||||
propTypes: {
|
propTypes: {
|
||||||
config: React.PropTypes.object.isRequired,
|
config: React.PropTypes.object,
|
||||||
ConferenceHandler: React.PropTypes.any,
|
ConferenceHandler: React.PropTypes.any,
|
||||||
onNewScreen: React.PropTypes.func,
|
onNewScreen: React.PropTypes.func,
|
||||||
registrationUrl: React.PropTypes.string,
|
registrationUrl: React.PropTypes.string,
|
||||||
|
@ -84,7 +86,8 @@ module.exports = React.createClass({
|
||||||
|
|
||||||
getDefaultProps: function() {
|
getDefaultProps: function() {
|
||||||
return {
|
return {
|
||||||
startingQueryParams: {}
|
startingQueryParams: {},
|
||||||
|
config: {},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -97,10 +100,9 @@ module.exports = React.createClass({
|
||||||
else if (window.localStorage && window.localStorage.getItem("mx_hs_url")) {
|
else if (window.localStorage && window.localStorage.getItem("mx_hs_url")) {
|
||||||
return window.localStorage.getItem("mx_hs_url");
|
return window.localStorage.getItem("mx_hs_url");
|
||||||
}
|
}
|
||||||
else if (this.props.config) {
|
else {
|
||||||
return this.props.config.default_hs_url
|
return this.props.config.default_hs_url || "https://matrix.org";
|
||||||
}
|
}
|
||||||
return "https://matrix.org";
|
|
||||||
},
|
},
|
||||||
|
|
||||||
getFallbackHsUrl: function() {
|
getFallbackHsUrl: function() {
|
||||||
|
@ -116,10 +118,9 @@ module.exports = React.createClass({
|
||||||
else if (window.localStorage && window.localStorage.getItem("mx_is_url")) {
|
else if (window.localStorage && window.localStorage.getItem("mx_is_url")) {
|
||||||
return window.localStorage.getItem("mx_is_url");
|
return window.localStorage.getItem("mx_is_url");
|
||||||
}
|
}
|
||||||
else if (this.props.config) {
|
else {
|
||||||
return this.props.config.default_is_url
|
return this.props.config.default_is_url || "https://vector.im"
|
||||||
}
|
}
|
||||||
return "https://matrix.org";
|
|
||||||
},
|
},
|
||||||
|
|
||||||
componentWillMount: function() {
|
componentWillMount: function() {
|
||||||
|
@ -466,48 +467,7 @@ module.exports = React.createClass({
|
||||||
//this._setPage(this.PageTypes.CreateRoom);
|
//this._setPage(this.PageTypes.CreateRoom);
|
||||||
//this.notifyNewScreen('new');
|
//this.notifyNewScreen('new');
|
||||||
|
|
||||||
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
createRoom().done();
|
||||||
var NeedToRegisterDialog = sdk.getComponent("dialogs.NeedToRegisterDialog");
|
|
||||||
var Loader = sdk.getComponent("elements.Spinner");
|
|
||||||
var modal = Modal.createDialog(Loader);
|
|
||||||
|
|
||||||
if (MatrixClientPeg.get().isGuest()) {
|
|
||||||
Modal.createDialog(NeedToRegisterDialog, {
|
|
||||||
title: "Please Register",
|
|
||||||
description: "Guest users can't create new rooms. Please register to create room and start a chat."
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// XXX: FIXME: deduplicate this with MemberInfo's 'start chat' impl
|
|
||||||
MatrixClientPeg.get().createRoom({
|
|
||||||
preset: "private_chat",
|
|
||||||
// Allow guests by default since the room is private and they'd
|
|
||||||
// need an invite. This means clicking on a 3pid invite email can
|
|
||||||
// actually drop you right in to a chat.
|
|
||||||
initial_state: [
|
|
||||||
{
|
|
||||||
content: {
|
|
||||||
guest_access: 'can_join'
|
|
||||||
},
|
|
||||||
type: 'm.room.guest_access',
|
|
||||||
state_key: '',
|
|
||||||
}
|
|
||||||
],
|
|
||||||
}).done(function(res) {
|
|
||||||
modal.close();
|
|
||||||
dis.dispatch({
|
|
||||||
action: 'view_room',
|
|
||||||
room_id: res.room_id,
|
|
||||||
// show_settings: true,
|
|
||||||
});
|
|
||||||
}, function(err) {
|
|
||||||
modal.close();
|
|
||||||
Modal.createDialog(ErrorDialog, {
|
|
||||||
title: "Failed to create room",
|
|
||||||
description: err.toString()
|
|
||||||
});
|
|
||||||
});
|
|
||||||
break;
|
break;
|
||||||
case 'view_room_directory':
|
case 'view_room_directory':
|
||||||
this._setPage(this.PageTypes.RoomDirectory);
|
this._setPage(this.PageTypes.RoomDirectory);
|
||||||
|
@ -1091,7 +1051,7 @@ module.exports = React.createClass({
|
||||||
right_panel = <RightPanel roomId={this.state.currentRoom} collapsed={this.state.collapse_rhs} opacity={this.state.sideOpacity} />
|
right_panel = <RightPanel roomId={this.state.currentRoom} collapsed={this.state.collapse_rhs} opacity={this.state.sideOpacity} />
|
||||||
break;
|
break;
|
||||||
case this.PageTypes.UserSettings:
|
case this.PageTypes.UserSettings:
|
||||||
page_element = <UserSettings onClose={this.onUserSettingsClose} version={this.state.version} />
|
page_element = <UserSettings onClose={this.onUserSettingsClose} version={this.state.version} brand={this.props.config.brand} />
|
||||||
right_panel = <RightPanel collapsed={this.state.collapse_rhs} opacity={this.state.sideOpacity}/>
|
right_panel = <RightPanel collapsed={this.state.collapse_rhs} opacity={this.state.sideOpacity}/>
|
||||||
break;
|
break;
|
||||||
case this.PageTypes.CreateRoom:
|
case this.PageTypes.CreateRoom:
|
||||||
|
@ -1159,6 +1119,7 @@ module.exports = React.createClass({
|
||||||
guestAccessToken={this.state.guestAccessToken}
|
guestAccessToken={this.state.guestAccessToken}
|
||||||
defaultHsUrl={this.props.config.default_hs_url}
|
defaultHsUrl={this.props.config.default_hs_url}
|
||||||
defaultIsUrl={this.props.config.default_is_url}
|
defaultIsUrl={this.props.config.default_is_url}
|
||||||
|
brand={this.props.config.brand}
|
||||||
customHsUrl={this.getCurrentHsUrl()}
|
customHsUrl={this.getCurrentHsUrl()}
|
||||||
customIsUrl={this.getCurrentIsUrl()}
|
customIsUrl={this.getCurrentIsUrl()}
|
||||||
registrationUrl={this.props.registrationUrl}
|
registrationUrl={this.props.registrationUrl}
|
||||||
|
|
|
@ -86,6 +86,10 @@ module.exports = React.createClass({
|
||||||
// to manage its animations
|
// to manage its animations
|
||||||
this._readReceiptMap = {};
|
this._readReceiptMap = {};
|
||||||
|
|
||||||
|
// Remember the read marker ghost node so we can do the cleanup that
|
||||||
|
// Velocity requires
|
||||||
|
this._readMarkerGhostNode = null;
|
||||||
|
|
||||||
this._isMounted = true;
|
this._isMounted = true;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -422,9 +426,16 @@ module.exports = React.createClass({
|
||||||
},
|
},
|
||||||
|
|
||||||
_startAnimation: function(ghostNode) {
|
_startAnimation: function(ghostNode) {
|
||||||
Velocity(ghostNode, {opacity: '0', width: '10%'},
|
if (this._readMarkerGhostNode) {
|
||||||
{duration: 400, easing: 'easeInSine',
|
Velocity.Utilities.removeData(this._readMarkerGhostNode);
|
||||||
delay: 1000});
|
}
|
||||||
|
this._readMarkerGhostNode = ghostNode;
|
||||||
|
|
||||||
|
if (ghostNode) {
|
||||||
|
Velocity(ghostNode, {opacity: '0', width: '10%'},
|
||||||
|
{duration: 400, easing: 'easeInSine',
|
||||||
|
delay: 1000});
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
_getReadMarkerGhostTile: function() {
|
_getReadMarkerGhostTile: function() {
|
||||||
|
|
|
@ -677,6 +677,16 @@ module.exports = React.createClass({
|
||||||
|
|
||||||
uploadFile: function(file) {
|
uploadFile: function(file) {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
|
if (MatrixClientPeg.get().isGuest()) {
|
||||||
|
var NeedToRegisterDialog = sdk.getComponent("dialogs.NeedToRegisterDialog");
|
||||||
|
Modal.createDialog(NeedToRegisterDialog, {
|
||||||
|
title: "Please Register",
|
||||||
|
description: "Guest users can't upload files. Please register to upload."
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
ContentMessages.sendContentToRoom(
|
ContentMessages.sendContentToRoom(
|
||||||
file, this.state.room.roomId, MatrixClientPeg.get()
|
file, this.state.room.roomId, MatrixClientPeg.get()
|
||||||
).done(undefined, function(error) {
|
).done(undefined, function(error) {
|
||||||
|
|
|
@ -31,7 +31,9 @@ module.exports = React.createClass({
|
||||||
|
|
||||||
propTypes: {
|
propTypes: {
|
||||||
version: React.PropTypes.string,
|
version: React.PropTypes.string,
|
||||||
onClose: React.PropTypes.func
|
onClose: React.PropTypes.func,
|
||||||
|
// The brand string given when creating email pushers
|
||||||
|
brand: React.PropTypes.string,
|
||||||
},
|
},
|
||||||
|
|
||||||
getDefaultProps: function() {
|
getDefaultProps: function() {
|
||||||
|
@ -244,6 +246,23 @@ module.exports = React.createClass({
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
_renderDeviceInfo: function() {
|
||||||
|
var client = MatrixClientPeg.get();
|
||||||
|
var deviceId = client.deviceId;
|
||||||
|
var olmKey = client.getDeviceEd25519Key() || "<not supported>";
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h3>Cryptography</h3>
|
||||||
|
<div className="mx_UserSettings_section">
|
||||||
|
<ul>
|
||||||
|
<li>Device ID: {deviceId}</li>
|
||||||
|
<li>Device key: {olmKey}</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
render: function() {
|
render: function() {
|
||||||
var self = this;
|
var self = this;
|
||||||
var Loader = sdk.getComponent("elements.Spinner");
|
var Loader = sdk.getComponent("elements.Spinner");
|
||||||
|
@ -299,7 +318,7 @@ module.exports = React.createClass({
|
||||||
onValueChanged={ this.onAddThreepidClicked } />
|
onValueChanged={ this.onAddThreepidClicked } />
|
||||||
</div>
|
</div>
|
||||||
<div className="mx_UserSettings_addThreepid">
|
<div className="mx_UserSettings_addThreepid">
|
||||||
<img src="img/plus.svg" width="14" height="14" alt="Add" onClick={ this.onAddThreepidClicked }/>
|
<img src="img/plus.svg" width="14" height="14" alt="Add" onClick={ this.onAddThreepidClicked.bind(this, undefined, true) }/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -333,7 +352,7 @@ module.exports = React.createClass({
|
||||||
<h3>Notifications</h3>
|
<h3>Notifications</h3>
|
||||||
|
|
||||||
<div className="mx_UserSettings_section">
|
<div className="mx_UserSettings_section">
|
||||||
<Notifications threepids={this.state.threepids} />
|
<Notifications threepids={this.state.threepids} brand={this.props.brand} />
|
||||||
</div>
|
</div>
|
||||||
</div>);
|
</div>);
|
||||||
}
|
}
|
||||||
|
@ -390,6 +409,8 @@ module.exports = React.createClass({
|
||||||
|
|
||||||
{notification_area}
|
{notification_area}
|
||||||
|
|
||||||
|
{this._renderDeviceInfo()}
|
||||||
|
|
||||||
<h3>Advanced</h3>
|
<h3>Advanced</h3>
|
||||||
|
|
||||||
<div className="mx_UserSettings_section">
|
<div className="mx_UserSettings_section">
|
||||||
|
@ -403,9 +424,8 @@ module.exports = React.createClass({
|
||||||
Identity Server is { MatrixClientPeg.get().getIdentityServerUrl() }
|
Identity Server is { MatrixClientPeg.get().getIdentityServerUrl() }
|
||||||
</div>
|
</div>
|
||||||
<div className="mx_UserSettings_advanced">
|
<div className="mx_UserSettings_advanced">
|
||||||
Version {this.state.clientVersion}
|
matrix-react-sdk version: {this.state.clientVersion}<br/>
|
||||||
<br />
|
vector-web version: {this.props.version}<br/>
|
||||||
{this.props.version}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -22,6 +22,7 @@ var sdk = require('../../../index');
|
||||||
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");
|
||||||
|
var MatrixClientPeg = require("../../../MatrixClientPeg");
|
||||||
var RegistrationForm = require("../../views/login/RegistrationForm");
|
var RegistrationForm = require("../../views/login/RegistrationForm");
|
||||||
var CaptchaForm = require("../../views/login/CaptchaForm");
|
var CaptchaForm = require("../../views/login/CaptchaForm");
|
||||||
|
|
||||||
|
@ -40,6 +41,7 @@ module.exports = React.createClass({
|
||||||
customIsUrl: React.PropTypes.string,
|
customIsUrl: React.PropTypes.string,
|
||||||
defaultHsUrl: React.PropTypes.string,
|
defaultHsUrl: React.PropTypes.string,
|
||||||
defaultIsUrl: React.PropTypes.string,
|
defaultIsUrl: React.PropTypes.string,
|
||||||
|
brand: React.PropTypes.string,
|
||||||
email: React.PropTypes.string,
|
email: React.PropTypes.string,
|
||||||
username: React.PropTypes.string,
|
username: React.PropTypes.string,
|
||||||
guestAccessToken: React.PropTypes.string,
|
guestAccessToken: React.PropTypes.string,
|
||||||
|
@ -145,6 +147,26 @@ module.exports = React.createClass({
|
||||||
identityServerUrl: self.registerLogic.getIdentityServerUrl(),
|
identityServerUrl: self.registerLogic.getIdentityServerUrl(),
|
||||||
accessToken: response.access_token
|
accessToken: response.access_token
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (self.props.brand) {
|
||||||
|
MatrixClientPeg.get().getPushers().done((resp)=>{
|
||||||
|
var pushers = resp.pushers;
|
||||||
|
for (var i = 0; i < pushers.length; ++i) {
|
||||||
|
if (pushers[i].kind == 'email') {
|
||||||
|
var emailPusher = pushers[i];
|
||||||
|
emailPusher.data = { brand: self.props.brand };
|
||||||
|
MatrixClientPeg.get().setPusher(emailPusher).done(() => {
|
||||||
|
console.log("Set email branding to " + self.props.brand);
|
||||||
|
}, (error) => {
|
||||||
|
console.error("Couldn't set email branding: " + error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, (error) => {
|
||||||
|
console.error("Couldn't get pushers: " + error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
}, function(err) {
|
}, function(err) {
|
||||||
if (err.message) {
|
if (err.message) {
|
||||||
self.setState({
|
self.setState({
|
||||||
|
|
|
@ -39,11 +39,11 @@ module.exports = React.createClass({
|
||||||
focus: true
|
focus: true
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
componentDidMount: function() {
|
componentDidMount: function() {
|
||||||
if (this.props.focus) {
|
if (this.props.focus) {
|
||||||
// Set the cursor at the end of the text input
|
// Set the cursor at the end of the text input
|
||||||
this.refs.textinput.value = this.props.value;
|
this.refs.textinput.value = this.props.value;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -83,13 +83,12 @@ module.exports = React.createClass({
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="mx_Dialog_buttons">
|
<div className="mx_Dialog_buttons">
|
||||||
<button onClick={this.onOk}>
|
|
||||||
{this.props.button}
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button onClick={this.onCancel}>
|
<button onClick={this.onCancel}>
|
||||||
Cancel
|
Cancel
|
||||||
</button>
|
</button>
|
||||||
|
<button onClick={this.onOk}>
|
||||||
|
{this.props.button}
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -17,8 +17,8 @@ limitations under the License.
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var React = require('react');
|
var React = require('react');
|
||||||
var Velocity = require('velocity-animate');
|
var Velocity = require('velocity-vector');
|
||||||
require('velocity-ui-pack');
|
require('velocity-vector/velocity.ui');
|
||||||
var sdk = require('../../../index');
|
var sdk = require('../../../index');
|
||||||
var Email = require('../../../email');
|
var Email = require('../../../email');
|
||||||
var Modal = require("../../../Modal");
|
var Modal = require("../../../Modal");
|
||||||
|
|
|
@ -45,9 +45,9 @@ module.exports = React.createClass({
|
||||||
|
|
||||||
getInitialState: function() {
|
getInitialState: function() {
|
||||||
return {
|
return {
|
||||||
// the URL (if any) to be previewed with a LinkPreviewWidget
|
// the URLs (if any) to be previewed with a LinkPreviewWidget
|
||||||
// inside this TextualBody.
|
// inside this TextualBody.
|
||||||
link: null,
|
links: [],
|
||||||
|
|
||||||
// track whether the preview widget is hidden
|
// track whether the preview widget is hidden
|
||||||
widgetHidden: false,
|
widgetHidden: false,
|
||||||
|
@ -57,9 +57,11 @@ module.exports = React.createClass({
|
||||||
componentDidMount: function() {
|
componentDidMount: function() {
|
||||||
linkifyElement(this.refs.content, linkifyMatrix.options);
|
linkifyElement(this.refs.content, linkifyMatrix.options);
|
||||||
|
|
||||||
var link = this.findLink(this.refs.content.children);
|
var links = this.findLinks(this.refs.content.children);
|
||||||
if (link) {
|
if (links.length) {
|
||||||
this.setState({ link: link.getAttribute("href") });
|
this.setState({ links: links.map((link)=>{
|
||||||
|
return link.getAttribute("href");
|
||||||
|
})});
|
||||||
|
|
||||||
// lazy-load the hidden state of the preview widget from localstorage
|
// lazy-load the hidden state of the preview widget from localstorage
|
||||||
if (global.localStorage) {
|
if (global.localStorage) {
|
||||||
|
@ -74,27 +76,32 @@ module.exports = React.createClass({
|
||||||
|
|
||||||
shouldComponentUpdate: function(nextProps, nextState) {
|
shouldComponentUpdate: function(nextProps, nextState) {
|
||||||
// exploit that events are immutable :)
|
// exploit that events are immutable :)
|
||||||
|
// ...and that .links is only ever set in componentDidMount and never changes
|
||||||
return (nextProps.mxEvent.getId() !== this.props.mxEvent.getId() ||
|
return (nextProps.mxEvent.getId() !== this.props.mxEvent.getId() ||
|
||||||
nextProps.highlights !== this.props.highlights ||
|
nextProps.highlights !== this.props.highlights ||
|
||||||
nextProps.highlightLink !== this.props.highlightLink ||
|
nextProps.highlightLink !== this.props.highlightLink ||
|
||||||
nextState.link !== this.state.link ||
|
nextState.links !== this.state.links ||
|
||||||
nextState.widgetHidden !== this.state.widgetHidden);
|
nextState.widgetHidden !== this.state.widgetHidden);
|
||||||
},
|
},
|
||||||
|
|
||||||
findLink: function(nodes) {
|
findLinks: function(nodes) {
|
||||||
|
var links = [];
|
||||||
for (var i = 0; i < nodes.length; i++) {
|
for (var i = 0; i < nodes.length; i++) {
|
||||||
var node = nodes[i];
|
var node = nodes[i];
|
||||||
if (node.tagName === "A" && node.getAttribute("href"))
|
if (node.tagName === "A" && node.getAttribute("href"))
|
||||||
{
|
{
|
||||||
return this.isLinkPreviewable(node) ? node : undefined;
|
if (this.isLinkPreviewable(node)) {
|
||||||
|
links.push(node);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if (node.tagName === "PRE" || node.tagName === "CODE") {
|
else if (node.tagName === "PRE" || node.tagName === "CODE") {
|
||||||
return;
|
continue;
|
||||||
}
|
}
|
||||||
else if (node.children && node.children.length) {
|
else if (node.children && node.children.length) {
|
||||||
return this.findLink(node.children)
|
links = links.concat(this.findLinks(node.children));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return links;
|
||||||
},
|
},
|
||||||
|
|
||||||
isLinkPreviewable: function(node) {
|
isLinkPreviewable: function(node) {
|
||||||
|
@ -160,14 +167,17 @@ module.exports = React.createClass({
|
||||||
{highlightLink: this.props.highlightLink});
|
{highlightLink: this.props.highlightLink});
|
||||||
|
|
||||||
|
|
||||||
var widget;
|
var widgets;
|
||||||
if (this.state.link && !this.state.widgetHidden) {
|
if (this.state.links.length && !this.state.widgetHidden) {
|
||||||
var LinkPreviewWidget = sdk.getComponent('rooms.LinkPreviewWidget');
|
var LinkPreviewWidget = sdk.getComponent('rooms.LinkPreviewWidget');
|
||||||
widget = <LinkPreviewWidget
|
widgets = this.state.links.map((link)=>{
|
||||||
link={ this.state.link }
|
return <LinkPreviewWidget
|
||||||
mxEvent={ this.props.mxEvent }
|
key={ link }
|
||||||
onCancelClick={ this.onCancelClick }
|
link={ link }
|
||||||
onWidgetLoad={ this.props.onWidgetLoad }/>;
|
mxEvent={ this.props.mxEvent }
|
||||||
|
onCancelClick={ this.onCancelClick }
|
||||||
|
onWidgetLoad={ this.props.onWidgetLoad }/>;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (content.msgtype) {
|
switch (content.msgtype) {
|
||||||
|
@ -176,21 +186,21 @@ module.exports = React.createClass({
|
||||||
return (
|
return (
|
||||||
<span ref="content" className="mx_MEmoteBody mx_EventTile_content">
|
<span ref="content" className="mx_MEmoteBody mx_EventTile_content">
|
||||||
* { name } { body }
|
* { name } { body }
|
||||||
{ widget }
|
{ widgets }
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
case "m.notice":
|
case "m.notice":
|
||||||
return (
|
return (
|
||||||
<span ref="content" className="mx_MNoticeBody mx_EventTile_content">
|
<span ref="content" className="mx_MNoticeBody mx_EventTile_content">
|
||||||
{ body }
|
{ body }
|
||||||
{ widget }
|
{ widgets }
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
default: // including "m.text"
|
default: // including "m.text"
|
||||||
return (
|
return (
|
||||||
<span ref="content" className="mx_MTextBody mx_EventTile_content">
|
<span ref="content" className="mx_MTextBody mx_EventTile_content">
|
||||||
{ body }
|
{ body }
|
||||||
{ widget }
|
{ widgets }
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -128,16 +128,24 @@ module.exports = React.createClass({
|
||||||
},
|
},
|
||||||
|
|
||||||
getInitialState: function() {
|
getInitialState: function() {
|
||||||
return {menu: false, allReadAvatars: false};
|
return {menu: false, allReadAvatars: false, verified: null};
|
||||||
},
|
},
|
||||||
|
|
||||||
componentWillMount: function() {
|
componentWillMount: function() {
|
||||||
// don't do RR animations until we are mounted
|
// don't do RR animations until we are mounted
|
||||||
this._suppressReadReceiptAnimation = true;
|
this._suppressReadReceiptAnimation = true;
|
||||||
|
this._verifyEvent(this.props.mxEvent);
|
||||||
},
|
},
|
||||||
|
|
||||||
componentDidMount: function() {
|
componentDidMount: function() {
|
||||||
this._suppressReadReceiptAnimation = false;
|
this._suppressReadReceiptAnimation = false;
|
||||||
|
MatrixClientPeg.get().on("deviceVerified", this.onDeviceVerified);
|
||||||
|
},
|
||||||
|
|
||||||
|
componentWillReceiveProps: function (nextProps) {
|
||||||
|
if (nextProps.mxEvent !== this.props.mxEvent) {
|
||||||
|
this._verifyEvent(nextProps.mxEvent);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
shouldComponentUpdate: function (nextProps, nextState) {
|
shouldComponentUpdate: function (nextProps, nextState) {
|
||||||
|
@ -152,6 +160,31 @@ module.exports = React.createClass({
|
||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
componentWillUnmount: function() {
|
||||||
|
var client = MatrixClientPeg.get();
|
||||||
|
if (client) {
|
||||||
|
client.removeListener("deviceVerified", this.onDeviceVerified);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
onDeviceVerified: function(userId, device) {
|
||||||
|
if (userId == this.props.mxEvent.getSender()) {
|
||||||
|
this._verifyEvent(this.props.mxEvent);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
_verifyEvent: function(mxEvent) {
|
||||||
|
var verified = null;
|
||||||
|
|
||||||
|
if (mxEvent.isEncrypted()) {
|
||||||
|
verified = MatrixClientPeg.get().isEventSenderVerified(mxEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
verified: verified
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
_propsEqual: function(objA, objB) {
|
_propsEqual: function(objA, objB) {
|
||||||
var keysA = Object.keys(objA);
|
var keysA = Object.keys(objA);
|
||||||
var keysB = Object.keys(objB);
|
var keysB = Object.keys(objB);
|
||||||
|
@ -346,6 +379,8 @@ module.exports = React.createClass({
|
||||||
mx_EventTile_last: this.props.last,
|
mx_EventTile_last: this.props.last,
|
||||||
mx_EventTile_contextual: this.props.contextual,
|
mx_EventTile_contextual: this.props.contextual,
|
||||||
menu: this.state.menu,
|
menu: this.state.menu,
|
||||||
|
mx_EventTile_verified: this.state.verified == true,
|
||||||
|
mx_EventTile_unverified: this.state.verified == false,
|
||||||
});
|
});
|
||||||
var timestamp = <a href={ "#/room/" + this.props.mxEvent.getRoomId() +"/"+ this.props.mxEvent.getId() }>
|
var timestamp = <a href={ "#/room/" + this.props.mxEvent.getRoomId() +"/"+ this.props.mxEvent.getId() }>
|
||||||
<MessageTimestamp ts={this.props.mxEvent.getTs()} />
|
<MessageTimestamp ts={this.props.mxEvent.getTs()} />
|
||||||
|
|
|
@ -26,6 +26,7 @@ module.exports = React.createClass({
|
||||||
propTypes: {
|
propTypes: {
|
||||||
roomId: React.PropTypes.string.isRequired,
|
roomId: React.PropTypes.string.isRequired,
|
||||||
onInvite: React.PropTypes.func.isRequired, // fn(inputText)
|
onInvite: React.PropTypes.func.isRequired, // fn(inputText)
|
||||||
|
onThirdPartyInvite: React.PropTypes.func.isRequired, // fn(inputText)
|
||||||
onSearchQueryChanged: React.PropTypes.func // fn(inputText)
|
onSearchQueryChanged: React.PropTypes.func // fn(inputText)
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -49,10 +50,19 @@ module.exports = React.createClass({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
componentDidMount: function() {
|
||||||
|
// initialise the email tile
|
||||||
|
this.onSearchQueryChanged('');
|
||||||
|
},
|
||||||
|
|
||||||
onInvite: function(ev) {
|
onInvite: function(ev) {
|
||||||
this.props.onInvite(this._input);
|
this.props.onInvite(this._input);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
onThirdPartyInvite: function(ev) {
|
||||||
|
this.props.onThirdPartyInvite(this._input);
|
||||||
|
},
|
||||||
|
|
||||||
onSearchQueryChanged: function(input) {
|
onSearchQueryChanged: function(input) {
|
||||||
this._input = input;
|
this._input = input;
|
||||||
var EntityTile = sdk.getComponent("rooms.EntityTile");
|
var EntityTile = sdk.getComponent("rooms.EntityTile");
|
||||||
|
@ -68,9 +78,10 @@ module.exports = React.createClass({
|
||||||
|
|
||||||
this._emailEntity = new Entities.newEntity(
|
this._emailEntity = new Entities.newEntity(
|
||||||
<EntityTile key="dynamic_invite_tile" suppressOnHover={true} showInviteButton={true}
|
<EntityTile key="dynamic_invite_tile" suppressOnHover={true} showInviteButton={true}
|
||||||
avatarJsx={ <BaseAvatar name="@" width={36} height={36} /> }
|
avatarJsx={ <BaseAvatar name="@" width={36} height={36} /> }
|
||||||
className="mx_EntityTile_invitePlaceholder"
|
className="mx_EntityTile_invitePlaceholder"
|
||||||
presenceState="online" onClick={this.onInvite} name={label} />,
|
presenceState="online" onClick={this.onThirdPartyInvite} name={"Invite by email"}
|
||||||
|
/>,
|
||||||
function(query) {
|
function(query) {
|
||||||
return true; // always show this
|
return true; // always show this
|
||||||
}
|
}
|
||||||
|
@ -89,7 +100,7 @@ module.exports = React.createClass({
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SearchableEntityList searchPlaceholderText={"Invite/search by name, email, id"}
|
<SearchableEntityList searchPlaceholderText={"Search/invite by name, email, id"}
|
||||||
onSubmit={this.props.onInvite}
|
onSubmit={this.props.onInvite}
|
||||||
onQueryChanged={this.onSearchQueryChanged}
|
onQueryChanged={this.onSearchQueryChanged}
|
||||||
entities={entities}
|
entities={entities}
|
||||||
|
|
55
src/components/views/rooms/MemberDeviceInfo.js
Normal file
55
src/components/views/rooms/MemberDeviceInfo.js
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
/*
|
||||||
|
Copyright 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 React = require('react');
|
||||||
|
var MatrixClientPeg = require("../../../MatrixClientPeg");
|
||||||
|
|
||||||
|
module.exports = React.createClass({
|
||||||
|
displayName: 'MemberDeviceInfo',
|
||||||
|
propTypes: {
|
||||||
|
userId: React.PropTypes.string.isRequired,
|
||||||
|
device: React.PropTypes.object.isRequired,
|
||||||
|
},
|
||||||
|
|
||||||
|
onVerifyClick: function() {
|
||||||
|
MatrixClientPeg.get().setDeviceVerified(this.props.userId,
|
||||||
|
this.props.device.id);
|
||||||
|
},
|
||||||
|
|
||||||
|
render: function() {
|
||||||
|
var indicator = null, button = null;
|
||||||
|
if (this.props.device.verified) {
|
||||||
|
indicator = (
|
||||||
|
<div className="mx_MemberDeviceInfo_verified">✔</div>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
button = (
|
||||||
|
<div className="mx_MemberDeviceInfo_textButton"
|
||||||
|
onClick={this.onVerifyClick}>
|
||||||
|
Verify
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<div className="mx_MemberDeviceInfo">
|
||||||
|
<div className="mx_MemberDeviceInfo_deviceId">{this.props.device.id}</div>
|
||||||
|
<div className="mx_MemberDeviceInfo_deviceKey">{this.props.device.key}</div>
|
||||||
|
{indicator}
|
||||||
|
{button}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
|
@ -30,27 +30,106 @@ var MatrixClientPeg = require("../../../MatrixClientPeg");
|
||||||
var dis = require("../../../dispatcher");
|
var dis = require("../../../dispatcher");
|
||||||
var Modal = require("../../../Modal");
|
var Modal = require("../../../Modal");
|
||||||
var sdk = require('../../../index');
|
var sdk = require('../../../index');
|
||||||
|
var createRoom = require('../../../createRoom');
|
||||||
|
|
||||||
module.exports = React.createClass({
|
module.exports = React.createClass({
|
||||||
displayName: 'MemberInfo',
|
displayName: 'MemberInfo',
|
||||||
|
|
||||||
|
propTypes: {
|
||||||
|
member: React.PropTypes.object.isRequired,
|
||||||
|
onFinished: React.PropTypes.func,
|
||||||
|
},
|
||||||
|
|
||||||
getDefaultProps: function() {
|
getDefaultProps: function() {
|
||||||
return {
|
return {
|
||||||
onFinished: function() {}
|
onFinished: function() {}
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
componentDidMount: function() {
|
getInitialState: function() {
|
||||||
// work out the current state
|
return {
|
||||||
if (this.props.member) {
|
can: {
|
||||||
var memberState = this._calculateOpsPermissions(this.props.member);
|
kick: false,
|
||||||
this.setState(memberState);
|
ban: false,
|
||||||
|
mute: false,
|
||||||
|
modifyLevel: false
|
||||||
|
},
|
||||||
|
muted: false,
|
||||||
|
isTargetMod: false,
|
||||||
|
updating: 0,
|
||||||
|
devicesLoading: true,
|
||||||
|
devices: null,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
|
componentWillMount: function() {
|
||||||
|
this._cancelDeviceList = null;
|
||||||
|
},
|
||||||
|
|
||||||
|
componentDidMount: function() {
|
||||||
|
this._updateStateForNewMember(this.props.member);
|
||||||
|
MatrixClientPeg.get().on("deviceVerified", this.onDeviceVerified);
|
||||||
|
},
|
||||||
|
|
||||||
componentWillReceiveProps: function(newProps) {
|
componentWillReceiveProps: function(newProps) {
|
||||||
var memberState = this._calculateOpsPermissions(newProps.member);
|
if (this.props.member.userId != newProps.member.userId) {
|
||||||
this.setState(memberState);
|
this._updateStateForNewMember(newProps.member);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
componentWillUnmount: function() {
|
||||||
|
var client = MatrixClientPeg.get();
|
||||||
|
if (client) {
|
||||||
|
client.removeListener("deviceVerified", this.onDeviceVerified);
|
||||||
|
}
|
||||||
|
if (this._cancelDeviceList) {
|
||||||
|
this._cancelDeviceList();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
onDeviceVerified: function(userId, device) {
|
||||||
|
if (userId == this.props.member.userId) {
|
||||||
|
// no need to re-download the whole thing; just update our copy of
|
||||||
|
// the list.
|
||||||
|
var devices = MatrixClientPeg.get().listDeviceKeys(userId);
|
||||||
|
this.setState({devices: devices});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
_updateStateForNewMember: function(member) {
|
||||||
|
var newState = this._calculateOpsPermissions(member);
|
||||||
|
newState.devicesLoading = true;
|
||||||
|
newState.devices = null;
|
||||||
|
this.setState(newState);
|
||||||
|
|
||||||
|
if (this._cancelDeviceList) {
|
||||||
|
this._cancelDeviceList();
|
||||||
|
this._cancelDeviceList = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._downloadDeviceList(member);
|
||||||
|
},
|
||||||
|
|
||||||
|
_downloadDeviceList: function(member) {
|
||||||
|
var cancelled = false;
|
||||||
|
this._cancelDeviceList = function() { cancelled = true; }
|
||||||
|
|
||||||
|
var client = MatrixClientPeg.get();
|
||||||
|
var self = this;
|
||||||
|
client.downloadKeys([member.userId], true).finally(function() {
|
||||||
|
self._cancelDeviceList = null;
|
||||||
|
}).done(function() {
|
||||||
|
if (cancelled) {
|
||||||
|
// we got cancelled - presumably a different user now
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var devices = client.listDeviceKeys(member.userId);
|
||||||
|
self.setState({devicesLoading: false, devices: devices});
|
||||||
|
}, function(err) {
|
||||||
|
console.log("Error downloading devices", err);
|
||||||
|
self.setState({devicesLoading: false});
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
onKick: function() {
|
onKick: function() {
|
||||||
|
@ -315,50 +394,15 @@ module.exports = React.createClass({
|
||||||
this.props.onFinished();
|
this.props.onFinished();
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
if (MatrixClientPeg.get().isGuest()) {
|
|
||||||
var NeedToRegisterDialog = sdk.getComponent("dialogs.NeedToRegisterDialog");
|
|
||||||
Modal.createDialog(NeedToRegisterDialog, {
|
|
||||||
title: "Please Register",
|
|
||||||
description: "Guest users can't create new rooms. Please register to create room and start a chat."
|
|
||||||
});
|
|
||||||
self.props.onFinished();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.setState({ updating: self.state.updating + 1 });
|
self.setState({ updating: self.state.updating + 1 });
|
||||||
MatrixClientPeg.get().createRoom({
|
createRoom({
|
||||||
// XXX: FIXME: deduplicate this with "view_create_room" in MatrixChat
|
createOpts: {
|
||||||
invite: [this.props.member.userId],
|
invite: [this.props.member.userId],
|
||||||
preset: "private_chat",
|
},
|
||||||
// Allow guests by default since the room is private and they'd
|
}).finally(function() {
|
||||||
// need an invite. This means clicking on a 3pid invite email can
|
self.props.onFinished();
|
||||||
// actually drop you right in to a chat.
|
|
||||||
initial_state: [
|
|
||||||
{
|
|
||||||
content: {
|
|
||||||
guest_access: 'can_join'
|
|
||||||
},
|
|
||||||
type: 'm.room.guest_access',
|
|
||||||
state_key: '',
|
|
||||||
}
|
|
||||||
],
|
|
||||||
}).then(
|
|
||||||
function(res) {
|
|
||||||
dis.dispatch({
|
|
||||||
action: 'view_room',
|
|
||||||
room_id: res.room_id
|
|
||||||
});
|
|
||||||
self.props.onFinished();
|
|
||||||
}, function(err) {
|
|
||||||
Modal.createDialog(ErrorDialog, {
|
|
||||||
title: "Failure to start chat",
|
|
||||||
description: err.message
|
|
||||||
});
|
|
||||||
self.props.onFinished();
|
|
||||||
}
|
|
||||||
).finally(()=>{
|
|
||||||
self.setState({ updating: self.state.updating - 1 });
|
self.setState({ updating: self.state.updating - 1 });
|
||||||
});
|
}).done();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -367,21 +411,7 @@ module.exports = React.createClass({
|
||||||
action: 'leave_room',
|
action: 'leave_room',
|
||||||
room_id: this.props.member.roomId,
|
room_id: this.props.member.roomId,
|
||||||
});
|
});
|
||||||
this.props.onFinished();
|
this.props.onFinished();
|
||||||
},
|
|
||||||
|
|
||||||
getInitialState: function() {
|
|
||||||
return {
|
|
||||||
can: {
|
|
||||||
kick: false,
|
|
||||||
ban: false,
|
|
||||||
mute: false,
|
|
||||||
modifyLevel: false
|
|
||||||
},
|
|
||||||
muted: false,
|
|
||||||
isTargetMod: false,
|
|
||||||
updating: 0,
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
_calculateOpsPermissions: function(member) {
|
_calculateOpsPermissions: function(member) {
|
||||||
|
@ -475,6 +505,36 @@ module.exports = React.createClass({
|
||||||
Modal.createDialog(ImageView, params, "mx_Dialog_lightbox");
|
Modal.createDialog(ImageView, params, "mx_Dialog_lightbox");
|
||||||
},
|
},
|
||||||
|
|
||||||
|
_renderDevices: function() {
|
||||||
|
var devices = this.state.devices;
|
||||||
|
var MemberDeviceInfo = sdk.getComponent('rooms.MemberDeviceInfo');
|
||||||
|
var Spinner = sdk.getComponent("elements.Spinner");
|
||||||
|
|
||||||
|
var devComponents;
|
||||||
|
if (this.state.devicesLoading) {
|
||||||
|
// still loading
|
||||||
|
devComponents = <Spinner />;
|
||||||
|
} else if (devices === null) {
|
||||||
|
devComponents = "Unable to load device list";
|
||||||
|
} else if (devices.length === 0) {
|
||||||
|
devComponents = "No registered devices";
|
||||||
|
} else {
|
||||||
|
devComponents = [];
|
||||||
|
for (var i = 0; i < devices.length; i++) {
|
||||||
|
devComponents.push(<MemberDeviceInfo key={i}
|
||||||
|
userId={this.props.member.userId}
|
||||||
|
device={devices[i]}/>);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h3>Devices</h3>
|
||||||
|
{devComponents}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
render: function() {
|
render: function() {
|
||||||
var startChat, kickButton, banButton, muteButton, giveModButton, spinner;
|
var startChat, kickButton, banButton, muteButton, giveModButton, spinner;
|
||||||
if (this.props.member.userId !== MatrixClientPeg.get().credentials.userId) {
|
if (this.props.member.userId !== MatrixClientPeg.get().credentials.userId) {
|
||||||
|
@ -551,6 +611,8 @@ module.exports = React.createClass({
|
||||||
|
|
||||||
{ startChat }
|
{ startChat }
|
||||||
|
|
||||||
|
{ this._renderDevices() }
|
||||||
|
|
||||||
{ adminTools }
|
{ adminTools }
|
||||||
|
|
||||||
{ spinner }
|
{ spinner }
|
||||||
|
@ -558,4 +620,3 @@ module.exports = React.createClass({
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -166,6 +166,25 @@ module.exports = React.createClass({
|
||||||
});
|
});
|
||||||
}, 500),
|
}, 500),
|
||||||
|
|
||||||
|
onThirdPartyInvite: function(inputText) {
|
||||||
|
var TextInputDialog = sdk.getComponent("dialogs.TextInputDialog");
|
||||||
|
Modal.createDialog(TextInputDialog, {
|
||||||
|
title: "Invite members by email",
|
||||||
|
description: "Please enter one or more email addresses",
|
||||||
|
value: inputText,
|
||||||
|
button: "Invite",
|
||||||
|
onFinished: (should_invite, addresses)=>{
|
||||||
|
if (should_invite) {
|
||||||
|
// defer the actual invite to the next event loop to give this
|
||||||
|
// Modal a chance to unmount in case onInvite() triggers a new one
|
||||||
|
setTimeout(()=>{
|
||||||
|
this.onInvite(addresses);
|
||||||
|
}, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
onInvite: function(inputText) {
|
onInvite: function(inputText) {
|
||||||
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||||
var NeedToRegisterDialog = sdk.getComponent("dialogs.NeedToRegisterDialog");
|
var NeedToRegisterDialog = sdk.getComponent("dialogs.NeedToRegisterDialog");
|
||||||
|
@ -387,7 +406,9 @@ module.exports = React.createClass({
|
||||||
// console.log(memberA + " and " + memberB + " have same power level");
|
// console.log(memberA + " and " + memberB + " have same power level");
|
||||||
if (memberA.name && memberB.name) {
|
if (memberA.name && memberB.name) {
|
||||||
// console.log("comparing names: " + memberA.name + " and " + memberB.name);
|
// console.log("comparing names: " + memberA.name + " and " + memberB.name);
|
||||||
return memberA.name.localeCompare(memberB.name);
|
var nameA = memberA.name[0] === '@' ? memberA.name.substr(1) : memberA.name;
|
||||||
|
var nameB = memberB.name[0] === '@' ? memberB.name.substr(1) : memberB.name;
|
||||||
|
return nameA.localeCompare(nameB);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -512,6 +533,7 @@ module.exports = React.createClass({
|
||||||
inviteMemberListSection = (
|
inviteMemberListSection = (
|
||||||
<InviteMemberList roomId={this.props.roomId}
|
<InviteMemberList roomId={this.props.roomId}
|
||||||
onSearchQueryChanged={this.onSearchQueryChanged}
|
onSearchQueryChanged={this.onSearchQueryChanged}
|
||||||
|
onThirdPartyInvite={this.onThirdPartyInvite}
|
||||||
onInvite={this.onInvite} />
|
onInvite={this.onInvite} />
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,6 +46,15 @@ module.exports = React.createClass({
|
||||||
},
|
},
|
||||||
|
|
||||||
onUploadClick: function(ev) {
|
onUploadClick: function(ev) {
|
||||||
|
if (MatrixClientPeg.get().isGuest()) {
|
||||||
|
var NeedToRegisterDialog = sdk.getComponent("dialogs.NeedToRegisterDialog");
|
||||||
|
Modal.createDialog(NeedToRegisterDialog, {
|
||||||
|
title: "Please Register",
|
||||||
|
description: "Guest users can't upload files. Please register to upload."
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this.refs.uploadInput.click();
|
this.refs.uploadInput.click();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -34,7 +34,7 @@ module.exports = React.createClass({
|
||||||
getInitialState: function() {
|
getInitialState: function() {
|
||||||
var tags = {};
|
var tags = {};
|
||||||
Object.keys(this.props.room.tags).forEach(function(tagName) {
|
Object.keys(this.props.room.tags).forEach(function(tagName) {
|
||||||
tags[tagName] = {};
|
tags[tagName] = ['yep'];
|
||||||
});
|
});
|
||||||
|
|
||||||
var areNotifsMuted = false;
|
var areNotifsMuted = false;
|
||||||
|
@ -180,7 +180,7 @@ module.exports = React.createClass({
|
||||||
// tags
|
// tags
|
||||||
if (this.state.tags_changed) {
|
if (this.state.tags_changed) {
|
||||||
var tagDiffs = ObjectUtils.getKeyValueArrayDiffs(originalState.tags, this.state.tags);
|
var tagDiffs = ObjectUtils.getKeyValueArrayDiffs(originalState.tags, this.state.tags);
|
||||||
// [ {place: add, key: "m.favourite", val: "yep"} ]
|
// [ {place: add, key: "m.favourite", val: ["yep"]} ]
|
||||||
tagDiffs.forEach(function(diff) {
|
tagDiffs.forEach(function(diff) {
|
||||||
switch (diff.place) {
|
switch (diff.place) {
|
||||||
case "add":
|
case "add":
|
||||||
|
|
|
@ -48,6 +48,7 @@ var SearchableEntityList = React.createClass({
|
||||||
getInitialState: function() {
|
getInitialState: function() {
|
||||||
return {
|
return {
|
||||||
query: "",
|
query: "",
|
||||||
|
focused: false,
|
||||||
truncateAt: this.props.truncateAt,
|
truncateAt: this.props.truncateAt,
|
||||||
results: this.getSearchResults("", this.props.entities)
|
results: this.getSearchResults("", this.props.entities)
|
||||||
};
|
};
|
||||||
|
@ -101,7 +102,7 @@ var SearchableEntityList = React.createClass({
|
||||||
|
|
||||||
getSearchResults: function(query, entities) {
|
getSearchResults: function(query, entities) {
|
||||||
if (!query || query.length === 0) {
|
if (!query || query.length === 0) {
|
||||||
return this.props.emptyQueryShowsAll ? entities : []
|
return this.props.emptyQueryShowsAll ? entities : [ entities[0] ]
|
||||||
}
|
}
|
||||||
return entities.filter(function(e) {
|
return entities.filter(function(e) {
|
||||||
return e.matches(query);
|
return e.matches(query);
|
||||||
|
@ -134,13 +135,27 @@ var SearchableEntityList = React.createClass({
|
||||||
<form onSubmit={this.onQuerySubmit} autoComplete="off">
|
<form onSubmit={this.onQuerySubmit} autoComplete="off">
|
||||||
<input className="mx_SearchableEntityList_query" id="mx_SearchableEntityList_query" type="text"
|
<input className="mx_SearchableEntityList_query" id="mx_SearchableEntityList_query" type="text"
|
||||||
onChange={this.onQueryChanged} value={this.state.query}
|
onChange={this.onQueryChanged} value={this.state.query}
|
||||||
|
onFocus={ ()=>{
|
||||||
|
if (this._blurTimeout) {
|
||||||
|
clearTimeout(this.blurTimeout);
|
||||||
|
}
|
||||||
|
this.setState({ focused: true });
|
||||||
|
} }
|
||||||
|
onBlur={ ()=>{
|
||||||
|
// nasty setTimeout heuristic to avoid the 'invite by email' prompt disappearing
|
||||||
|
// due to the onBlur before we can click on it
|
||||||
|
this._blurTimeout = setTimeout(
|
||||||
|
()=>{ this.setState({ focused: false }) },
|
||||||
|
300
|
||||||
|
);
|
||||||
|
} }
|
||||||
placeholder={this.props.searchPlaceholderText} />
|
placeholder={this.props.searchPlaceholderText} />
|
||||||
</form>
|
</form>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
var list;
|
var list;
|
||||||
if (this.state.results.length) {
|
if (this.state.results.length > 1 || this.state.focused) {
|
||||||
if (this.props.truncateAt) { // caller wants list truncated
|
if (this.props.truncateAt) { // caller wants list truncated
|
||||||
var TruncatedList = sdk.getComponent("elements.TruncatedList");
|
var TruncatedList = sdk.getComponent("elements.TruncatedList");
|
||||||
list = (
|
list = (
|
||||||
|
@ -172,10 +187,10 @@ var SearchableEntityList = React.createClass({
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={ "mx_SearchableEntityList " + (this.state.query.length ? "mx_SearchableEntityList_expanded" : "") }>
|
<div className={ "mx_SearchableEntityList " + (list ? "mx_SearchableEntityList_expanded" : "") }>
|
||||||
{ inputBox }
|
{ inputBox }
|
||||||
{ list }
|
{ list }
|
||||||
{ this.state.query.length ? <div className="mx_SearchableEntityList_hrWrapper"><hr/></div> : '' }
|
{ list ? <div className="mx_SearchableEntityList_hrWrapper"><hr/></div> : '' }
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
86
src/createRoom.js
Normal file
86
src/createRoom.js
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
/*
|
||||||
|
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 MatrixClientPeg = require('./MatrixClientPeg');
|
||||||
|
var Modal = require('./Modal');
|
||||||
|
var sdk = require('./index');
|
||||||
|
var dis = require("./dispatcher");
|
||||||
|
|
||||||
|
var q = require('q');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new room, and switch to it.
|
||||||
|
*
|
||||||
|
* Returns a promise which resolves to the room id, or null if the
|
||||||
|
* action was aborted or failed.
|
||||||
|
*
|
||||||
|
* @param {object=} opts parameters for creating the room
|
||||||
|
* @param {object=} opts.createOpts set of options to pass to createRoom call.
|
||||||
|
*/
|
||||||
|
function createRoom(opts) {
|
||||||
|
var opts = opts || {};
|
||||||
|
|
||||||
|
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||||
|
var NeedToRegisterDialog = sdk.getComponent("dialogs.NeedToRegisterDialog");
|
||||||
|
var Loader = sdk.getComponent("elements.Spinner");
|
||||||
|
|
||||||
|
var client = MatrixClientPeg.get();
|
||||||
|
if (client.isGuest()) {
|
||||||
|
Modal.createDialog(NeedToRegisterDialog, {
|
||||||
|
title: "Please Register",
|
||||||
|
description: "Guest users can't create new rooms. Please register to create room and start a chat."
|
||||||
|
});
|
||||||
|
return q(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
// set some defaults for the creation
|
||||||
|
var createOpts = opts.createOpts || {};
|
||||||
|
createOpts.preset = createOpts.preset || 'private_chat';
|
||||||
|
createOpts.visibility = createOpts.visibility || 'private';
|
||||||
|
|
||||||
|
// Allow guests by default since the room is private and they'd
|
||||||
|
// need an invite. This means clicking on a 3pid invite email can
|
||||||
|
// actually drop you right in to a chat.
|
||||||
|
createOpts.initial_state = createOpts.initial_state || [
|
||||||
|
{
|
||||||
|
content: {
|
||||||
|
guest_access: 'can_join'
|
||||||
|
},
|
||||||
|
type: 'm.room.guest_access',
|
||||||
|
state_key: '',
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
var modal = Modal.createDialog(Loader);
|
||||||
|
|
||||||
|
return client.createRoom(createOpts).finally(function() {
|
||||||
|
modal.close();
|
||||||
|
}).then(function(res) {
|
||||||
|
dis.dispatch({
|
||||||
|
action: 'view_room',
|
||||||
|
room_id: res.room_id
|
||||||
|
});
|
||||||
|
return res.room_id;
|
||||||
|
}, function(err) {
|
||||||
|
Modal.createDialog(ErrorDialog, {
|
||||||
|
title: "Failure to create room",
|
||||||
|
description: err.toString()
|
||||||
|
});
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = createRoom;
|
Loading…
Reference in a new issue