Merge remote-tracking branch 'origin/develop' into dbkr/scalar

This commit is contained in:
David Baker 2016-06-06 17:19:38 +01:00
commit fdcebe1e56
21 changed files with 243 additions and 54 deletions

View file

@ -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)

View file

@ -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

View file

@ -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": {
@ -31,7 +31,7 @@
"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": "^0.5.4",
"optimist": "^0.6.1", "optimist": "^0.6.1",
"q": "^1.4.1", "q": "^1.4.1",
"react": "^15.0.1", "react": "^15.0.1",

View file

@ -293,8 +293,9 @@ class Register extends Signup {
class Login extends Signup { class Login extends Signup {
constructor(hsUrl, isUrl) { constructor(hsUrl, isUrl, fallbackHsUrl) {
super(hsUrl, isUrl); super(hsUrl, isUrl);
this._fallbackHsUrl = fallbackHsUrl;
this._currentFlowIndex = 0; this._currentFlowIndex = 0;
this._flows = []; this._flows = [];
} }
@ -359,6 +360,30 @@ class Login extends Signup {
error.friendlyText = ( error.friendlyText = (
'Incorrect username and/or password.' 'Incorrect username and/or password.'
); );
if (self._fallbackHsUrl) {
// as per elsewhere, it would be much nicer to not replace the global
// client just to try an alternate HS
MatrixClientPeg.replaceUsingUrls(
self._fallbackHsUrl,
self._isUrl
);
return MatrixClientPeg.get().login('m.login.password', loginParams).then(function(data) {
return q({
homeserverUrl: self._fallbackHsUrl,
identityServerUrl: self._isUrl,
userId: data.user_id,
accessToken: data.access_token
});
}, function(fallback_error) {
// We also have to put the default back again if it fails...
MatrixClientPeg.replaceUsingUrls(
this._hsUrl,
this._isUrl
);
// throw the original error
throw error;
});
}
} }
else { else {
error.friendlyText = ( error.friendlyText = (

View file

@ -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
}); });
}, },

View file

@ -106,6 +106,18 @@ module.exports = React.createClass({
}); });
//console.log("enter: "+JSON.stringify(node.props._restingStyle)); //console.log("enter: "+JSON.stringify(node.props._restingStyle));
} else if (node === null) {
// Velocity stores data on elements using the jQuery .data()
// method, and assumes you'll be using jQuery's .remove() to
// remove the element, but we don't use jQuery, so we need to
// blow away the element's data explicitly otherwise it will leak.
// This uses Velocity's internal jQuery compatible wrapper.
// See the bug at
// https://github.com/julianshapiro/velocity/issues/300
// and the FAQ entry, "Preventing memory leaks when
// creating/destroying large numbers of elements"
// (https://github.com/julianshapiro/velocity/issues/47)
Velocity.Utilities.removeData(this.nodes[k]);
} }
this.nodes[k] = node; this.nodes[k] = node;
}, },

View file

@ -104,6 +104,10 @@ module.exports = React.createClass({
return "https://matrix.org"; return "https://matrix.org";
}, },
getFallbackHsUrl: function() {
return this.props.config.fallback_hs_url;
},
getCurrentIsUrl: function() { getCurrentIsUrl: function() {
if (this.state.register_is_url) { if (this.state.register_is_url) {
return this.state.register_is_url; return this.state.register_is_url;
@ -490,6 +494,7 @@ module.exports = React.createClass({
}, },
type: 'm.room.guest_access', type: 'm.room.guest_access',
state_key: '', state_key: '',
visibility: 'private',
} }
], ],
}).done(function(res) { }).done(function(res) {
@ -1157,6 +1162,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}
@ -1185,6 +1191,7 @@ module.exports = React.createClass({
defaultIsUrl={this.props.config.default_is_url} defaultIsUrl={this.props.config.default_is_url}
customHsUrl={this.getCurrentHsUrl()} customHsUrl={this.getCurrentHsUrl()}
customIsUrl={this.getCurrentIsUrl()} customIsUrl={this.getCurrentIsUrl()}
fallbackHsUrl={this.getFallbackHsUrl()}
onForgotPasswordClick={this.onForgotPasswordClick} onForgotPasswordClick={this.onForgotPasswordClick}
onLoginAsGuestClick={this.props.enableGuest && this.props.config && this.props.config.default_hs_url ? this._registerAsGuest.bind(this, true) : undefined} onLoginAsGuestClick={this.props.enableGuest && this.props.config && this.props.config.default_hs_url ? this._registerAsGuest.bind(this, true) : undefined}
onCancelClick={ this.state.guestCreds ? this.onReturnToGuestClick : null } onCancelClick={ this.state.guestCreds ? this.onReturnToGuestClick : null }

View file

@ -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) {

View file

@ -401,7 +401,7 @@ var TimelinePanel = React.createClass({
// if we are scrolled to the bottom, do a quick-reset of our unreadNotificationCount // if we are scrolled to the bottom, do a quick-reset of our unreadNotificationCount
// to avoid having to wait from the remote echo from the homeserver. // to avoid having to wait from the remote echo from the homeserver.
if (this.getScrollState().stuckAtBottom) { if (this.isAtEndOfLiveTimeline()) {
this.props.room.setUnreadNotificationCount('total', 0); this.props.room.setUnreadNotificationCount('total', 0);
this.props.room.setUnreadNotificationCount('highlight', 0); this.props.room.setUnreadNotificationCount('highlight', 0);
// XXX: i'm a bit surprised we don't have to emit an event or dispatch to get this picked up // XXX: i'm a bit surprised we don't have to emit an event or dispatch to get this picked up

View file

@ -299,7 +299,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>
); );
@ -397,9 +397,14 @@ module.exports = React.createClass({
Logged in as {this._me} Logged in as {this._me}
</div> </div>
<div className="mx_UserSettings_advanced"> <div className="mx_UserSettings_advanced">
Version {this.state.clientVersion} Homeserver is { MatrixClientPeg.get().getHomeserverUrl() }
<br /> </div>
{this.props.version} <div className="mx_UserSettings_advanced">
Identity Server is { MatrixClientPeg.get().getIdentityServerUrl() }
</div>
<div className="mx_UserSettings_advanced">
matrix-react-sdk version: {this.state.clientVersion}<br/>
vector-web version: {this.props.version}<br/>
</div> </div>
</div> </div>

View file

@ -35,6 +35,10 @@ module.exports = React.createClass({displayName: 'Login',
customIsUrl: React.PropTypes.string, customIsUrl: React.PropTypes.string,
defaultHsUrl: React.PropTypes.string, defaultHsUrl: React.PropTypes.string,
defaultIsUrl: React.PropTypes.string, defaultIsUrl: React.PropTypes.string,
// Secondary HS which we try to log into if the user is using
// the default HS but login fails. Useful for migrating to a
// different home server without confusing users.
fallbackHsUrl: 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,
@ -105,7 +109,9 @@ module.exports = React.createClass({displayName: 'Login',
hsUrl = hsUrl || this.state.enteredHomeserverUrl; hsUrl = hsUrl || this.state.enteredHomeserverUrl;
isUrl = isUrl || this.state.enteredIdentityServerUrl; isUrl = isUrl || this.state.enteredIdentityServerUrl;
var loginLogic = new Signup.Login(hsUrl, isUrl); var fallbackHsUrl = hsUrl == this.props.defaultHsUrl ? this.props.fallbackHsUrl : null;
var loginLogic = new Signup.Login(hsUrl, isUrl, fallbackHsUrl);
this._loginLogic = loginLogic; this._loginLogic = loginLogic;
loginLogic.getFlows().then(function(flows) { loginLogic.getFlows().then(function(flows) {

View file

@ -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({

View file

@ -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>
); );

View file

@ -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) {
@ -117,7 +124,7 @@ module.exports = React.createClass({
else { else {
var url = node.getAttribute("href"); var url = node.getAttribute("href");
var host = url.match(/^https?:\/\/(.*?)(\/|$)/)[1]; var host = url.match(/^https?:\/\/(.*?)(\/|$)/)[1];
if (node.textContent.trim().startsWith(host)) { if (node.textContent.toLowerCase().trim().startsWith(host.toLowerCase())) {
// it's a "foo.pl" style link // it's a "foo.pl" style link
return; return;
} }
@ -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>
); );
} }

View file

@ -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}

View file

@ -340,6 +340,7 @@ module.exports = React.createClass({
}, },
type: 'm.room.guest_access', type: 'm.room.guest_access',
state_key: '', state_key: '',
visibility: 'private',
} }
], ],
}).then( }).then(

View file

@ -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");
@ -514,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} />
); );
} }

View file

@ -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();
}, },

View file

@ -36,7 +36,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;
@ -186,7 +186,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":

View file

@ -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>
); );
} }