From 39a3d6fdd4e5fd9be6c42f87d329c62b7b3a4450 Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Tue, 24 May 2016 00:54:20 +0100 Subject: [PATCH 01/47] multiple URL preview support --- src/components/views/messages/TextualBody.js | 49 ++++++++++++-------- 1 file changed, 29 insertions(+), 20 deletions(-) diff --git a/src/components/views/messages/TextualBody.js b/src/components/views/messages/TextualBody.js index 223eabdc36..58e33f7eb3 100644 --- a/src/components/views/messages/TextualBody.js +++ b/src/components/views/messages/TextualBody.js @@ -45,9 +45,9 @@ module.exports = React.createClass({ getInitialState: function() { return { - // the URL (if any) to be previewed with a LinkPreviewWidget + // the URLs (if any) to be previewed with a LinkPreviewWidget // inside this TextualBody. - link: null, + links: null, // track whether the preview widget is hidden widgetHidden: false, @@ -57,9 +57,11 @@ module.exports = React.createClass({ componentDidMount: function() { linkifyElement(this.refs.content, linkifyMatrix.options); - var link = this.findLink(this.refs.content.children); - if (link) { - this.setState({ link: link.getAttribute("href") }); + var links = this.findLinks(this.refs.content.children); + if (links.length) { + this.setState({ links: links.map((link)=>{ + return link.getAttribute("href"); + })}); // lazy-load the hidden state of the preview widget from localstorage if (global.localStorage) { @@ -77,24 +79,28 @@ module.exports = React.createClass({ return (nextProps.mxEvent.getId() !== this.props.mxEvent.getId() || nextProps.highlights !== this.props.highlights || nextProps.highlightLink !== this.props.highlightLink || - nextState.link !== this.state.link || + nextState.links !== this.state.links || nextState.widgetHidden !== this.state.widgetHidden); }, - findLink: function(nodes) { + findLinks: function(nodes) { + var links = []; for (var i = 0; i < nodes.length; i++) { var node = nodes[i]; 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") { - return; + continue; } else if (node.children && node.children.length) { - return this.findLink(node.children) + links = links.concat(this.findLinks(node.children)); } } + return links; }, isLinkPreviewable: function(node) { @@ -160,14 +166,17 @@ module.exports = React.createClass({ {highlightLink: this.props.highlightLink}); - var widget; - if (this.state.link && !this.state.widgetHidden) { + var widgets; + if (this.state.links && !this.state.widgetHidden) { var LinkPreviewWidget = sdk.getComponent('rooms.LinkPreviewWidget'); - widget = ; + widgets = this.state.links.map((link)=>{ + return ; + }); } switch (content.msgtype) { @@ -176,21 +185,21 @@ module.exports = React.createClass({ return ( * { name } { body } - { widget } + { widgets } ); case "m.notice": return ( { body } - { widget } + { widgets } ); default: // including "m.text" return ( { body } - { widget } + { widgets } ); } From b3638f9169d481840c8adb2e751ca55e8ab3a29a Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Tue, 31 May 2016 19:42:00 +0100 Subject: [PATCH 02/47] PR review --- src/components/views/messages/TextualBody.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/components/views/messages/TextualBody.js b/src/components/views/messages/TextualBody.js index 58e33f7eb3..7743d5de7a 100644 --- a/src/components/views/messages/TextualBody.js +++ b/src/components/views/messages/TextualBody.js @@ -47,7 +47,7 @@ module.exports = React.createClass({ return { // the URLs (if any) to be previewed with a LinkPreviewWidget // inside this TextualBody. - links: null, + links: [], // track whether the preview widget is hidden widgetHidden: false, @@ -76,6 +76,7 @@ module.exports = React.createClass({ shouldComponentUpdate: function(nextProps, nextState) { // 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() || nextProps.highlights !== this.props.highlights || nextProps.highlightLink !== this.props.highlightLink || @@ -167,7 +168,7 @@ module.exports = React.createClass({ var widgets; - if (this.state.links && !this.state.widgetHidden) { + if (this.state.links.length && !this.state.widgetHidden) { var LinkPreviewWidget = sdk.getComponent('rooms.LinkPreviewWidget'); widgets = this.state.links.map((link)=>{ return Date: Tue, 31 May 2016 21:44:11 +0100 Subject: [PATCH 03/47] fix tag unsetting https://github.com/vector-im/vector-web/issues/1499 - HOW DID THIS EVER WORK?!?! --- src/components/views/rooms/RoomSettings.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/views/rooms/RoomSettings.js b/src/components/views/rooms/RoomSettings.js index fd8bcbfe96..8764700c5a 100644 --- a/src/components/views/rooms/RoomSettings.js +++ b/src/components/views/rooms/RoomSettings.js @@ -34,7 +34,7 @@ module.exports = React.createClass({ getInitialState: function() { var tags = {}; Object.keys(this.props.room.tags).forEach(function(tagName) { - tags[tagName] = {}; + tags[tagName] = ['yep']; }); var areNotifsMuted = false; @@ -180,7 +180,7 @@ module.exports = React.createClass({ // tags if (this.state.tags_changed) { 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) { switch (diff.place) { case "add": From e1ba7df66e4970c7506e9f882bae07bd4ec47628 Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Tue, 31 May 2016 23:50:21 +0100 Subject: [PATCH 04/47] fix the Add button for email addies --- src/components/structures/UserSettings.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/structures/UserSettings.js b/src/components/structures/UserSettings.js index 0be6271ea4..a0c03731da 100644 --- a/src/components/structures/UserSettings.js +++ b/src/components/structures/UserSettings.js @@ -299,7 +299,7 @@ module.exports = React.createClass({ onValueChanged={ this.onAddThreepidClicked } />
- Add + Add
); From c3fc76cdaa554d604c739806d59d4a14eac1e38a Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Tue, 31 May 2016 23:59:36 +0100 Subject: [PATCH 05/47] warn guests to register nicely to upload files --- src/components/structures/RoomView.js | 10 ++++++++++ src/components/views/rooms/MessageComposer.js | 9 +++++++++ 2 files changed, 19 insertions(+) diff --git a/src/components/structures/RoomView.js b/src/components/structures/RoomView.js index 33bbb510e3..77080b5a75 100644 --- a/src/components/structures/RoomView.js +++ b/src/components/structures/RoomView.js @@ -677,6 +677,16 @@ module.exports = React.createClass({ uploadFile: function(file) { 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( file, this.state.room.roomId, MatrixClientPeg.get() ).done(undefined, function(error) { diff --git a/src/components/views/rooms/MessageComposer.js b/src/components/views/rooms/MessageComposer.js index 20785c4c70..18d138f013 100644 --- a/src/components/views/rooms/MessageComposer.js +++ b/src/components/views/rooms/MessageComposer.js @@ -46,6 +46,15 @@ module.exports = React.createClass({ }, 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(); }, From 272afe39dc15284d921d159ebf1a1284824b609f Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Wed, 1 Jun 2016 02:03:53 +0100 Subject: [PATCH 06/47] do not list rooms by default --- src/components/structures/MatrixChat.js | 1 + src/components/views/rooms/MemberInfo.js | 1 + 2 files changed, 2 insertions(+) diff --git a/src/components/structures/MatrixChat.js b/src/components/structures/MatrixChat.js index 2f7a6ed8ec..8ee395a218 100644 --- a/src/components/structures/MatrixChat.js +++ b/src/components/structures/MatrixChat.js @@ -492,6 +492,7 @@ module.exports = React.createClass({ }, type: 'm.room.guest_access', state_key: '', + visibility: 'private', } ], }).done(function(res) { diff --git a/src/components/views/rooms/MemberInfo.js b/src/components/views/rooms/MemberInfo.js index 76e5af7612..889bc7bd8a 100644 --- a/src/components/views/rooms/MemberInfo.js +++ b/src/components/views/rooms/MemberInfo.js @@ -340,6 +340,7 @@ module.exports = React.createClass({ }, type: 'm.room.guest_access', state_key: '', + visibility: 'private', } ], }).then( From 0747ca392350f0fba25b4e076ff7152d63391fe8 Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Thu, 19 May 2016 23:00:28 +0100 Subject: [PATCH 07/47] ignore @ prefixes when sorting memberlist --- src/components/views/rooms/MemberList.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/components/views/rooms/MemberList.js b/src/components/views/rooms/MemberList.js index f029c519bc..a1769ad5f3 100644 --- a/src/components/views/rooms/MemberList.js +++ b/src/components/views/rooms/MemberList.js @@ -387,7 +387,9 @@ module.exports = React.createClass({ // console.log(memberA + " and " + memberB + " have same power level"); if (memberA.name && 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 { return 0; From 3b34311e0514b0c14ac60fec4fd398546dbc4551 Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Wed, 1 Jun 2016 23:42:34 +0100 Subject: [PATCH 08/47] implement new UX for 3pid invites --- src/components/views/rooms/InviteMemberList.js | 17 ++++++++++++++--- src/components/views/rooms/MemberInfo.js | 2 +- src/components/views/rooms/MemberList.js | 16 ++++++++++++++++ .../views/rooms/SearchableEntityList.js | 13 ++++++++----- 4 files changed, 39 insertions(+), 9 deletions(-) diff --git a/src/components/views/rooms/InviteMemberList.js b/src/components/views/rooms/InviteMemberList.js index 480066771b..8f8b22eaa7 100644 --- a/src/components/views/rooms/InviteMemberList.js +++ b/src/components/views/rooms/InviteMemberList.js @@ -26,6 +26,7 @@ module.exports = React.createClass({ propTypes: { roomId: React.PropTypes.string.isRequired, onInvite: React.PropTypes.func.isRequired, // fn(inputText) + onThirdPartyInvite: React.PropTypes.func.isRequired, // 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) { this.props.onInvite(this._input); }, + onThirdPartyInvite: function(ev) { + this.props.onThirdPartyInvite(this._input); + }, + onSearchQueryChanged: function(input) { this._input = input; var EntityTile = sdk.getComponent("rooms.EntityTile"); @@ -68,9 +78,10 @@ module.exports = React.createClass({ this._emailEntity = new Entities.newEntity( } - className="mx_EntityTile_invitePlaceholder" - presenceState="online" onClick={this.onInvite} name={label} />, + avatarJsx={ } + className="mx_EntityTile_invitePlaceholder" + presenceState="online" onClick={this.onThirdPartyInvite} name={"Invite by email"} + />, function(query) { return true; // always show this } diff --git a/src/components/views/rooms/MemberInfo.js b/src/components/views/rooms/MemberInfo.js index 889bc7bd8a..ba4a3734f5 100644 --- a/src/components/views/rooms/MemberInfo.js +++ b/src/components/views/rooms/MemberInfo.js @@ -368,7 +368,7 @@ module.exports = React.createClass({ action: 'leave_room', room_id: this.props.member.roomId, }); - this.props.onFinished(); + this.props.onFinished(); }, getInitialState: function() { diff --git a/src/components/views/rooms/MemberList.js b/src/components/views/rooms/MemberList.js index a1769ad5f3..21c0827fcc 100644 --- a/src/components/views/rooms/MemberList.js +++ b/src/components/views/rooms/MemberList.js @@ -166,6 +166,21 @@ module.exports = React.createClass({ }); }, 500), + onThirdPartyInvite: function(inputText) { + var TextInputDialog = sdk.getComponent("dialogs.TextInputDialog"); + Modal.createDialog(TextInputDialog, { + title: "Invite members by email", + description: "Please enter the email addresses to be invited (comma separated)", + value: inputText, + button: "Invite", + onFinished: (should_invite, addresses)=>{ + if (should_invite) { + this.onInvite(addresses); + } + } + }); + }, + onInvite: function(inputText) { var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); var NeedToRegisterDialog = sdk.getComponent("dialogs.NeedToRegisterDialog"); @@ -514,6 +529,7 @@ module.exports = React.createClass({ inviteMemberListSection = ( ); } diff --git a/src/components/views/rooms/SearchableEntityList.js b/src/components/views/rooms/SearchableEntityList.js index c09fc2faee..28e7b1785c 100644 --- a/src/components/views/rooms/SearchableEntityList.js +++ b/src/components/views/rooms/SearchableEntityList.js @@ -48,6 +48,7 @@ var SearchableEntityList = React.createClass({ getInitialState: function() { return { query: "", + focused: false, truncateAt: this.props.truncateAt, results: this.getSearchResults("", this.props.entities) }; @@ -101,7 +102,7 @@ var SearchableEntityList = React.createClass({ getSearchResults: function(query, entities) { if (!query || query.length === 0) { - return this.props.emptyQueryShowsAll ? entities : [] + return this.props.emptyQueryShowsAll ? entities : [ entities[0] ] } return entities.filter(function(e) { return e.matches(query); @@ -128,19 +129,21 @@ var SearchableEntityList = React.createClass({ render: function() { var inputBox; - + if (this.props.showInputBox) { inputBox = (
{ this.setState({ focused: true }) } } + onBlur={ ()=>{ this.setState({ focused: false }) } } placeholder={this.props.searchPlaceholderText} />
); } var list; - if (this.state.results.length) { + if (this.state.results.length > 1 || this.state.focused) { if (this.props.truncateAt) { // caller wants list truncated var TruncatedList = sdk.getComponent("elements.TruncatedList"); list = ( @@ -172,10 +175,10 @@ var SearchableEntityList = React.createClass({ } return ( -
+
{ inputBox } { list } - { this.state.query.length ?

: '' } + { list ?

: '' }
); } From 83fd02bdfadf78ce2309da15fbae95e7878e3e56 Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Thu, 2 Jun 2016 11:50:00 +0100 Subject: [PATCH 09/47] specify a brand when registering accounts --- src/Signup.js | 7 ++++++- src/components/structures/MatrixChat.js | 1 + src/components/structures/login/Registration.js | 2 ++ 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/Signup.js b/src/Signup.js index 4518955d95..c4fce88262 100644 --- a/src/Signup.js +++ b/src/Signup.js @@ -51,6 +51,7 @@ class Register extends Signup { this.username = undefined; // desired this.email = undefined; // desired this.password = undefined; // desired + this.brand = undefined; // optional brand to let the HS brand its mail notifs } setClientSecret(secret) { @@ -73,6 +74,10 @@ class Register extends Signup { this.guestAccessToken = token; } + setBrand(brand) { + this.brand = brand; + } + getStep() { return this._step; } @@ -131,7 +136,7 @@ class Register extends Signup { return MatrixClientPeg.get().register( this.username, this.password, this.params.sessionId, authDict, bindEmail, - this.guestAccessToken + this.guestAccessToken, this.brand ).then(function(result) { self.credentials = result; self.setStep("COMPLETE"); diff --git a/src/components/structures/MatrixChat.js b/src/components/structures/MatrixChat.js index 8ee395a218..f708a3e0fb 100644 --- a/src/components/structures/MatrixChat.js +++ b/src/components/structures/MatrixChat.js @@ -1160,6 +1160,7 @@ module.exports = React.createClass({ guestAccessToken={this.state.guestAccessToken} defaultHsUrl={this.props.config.default_hs_url} defaultIsUrl={this.props.config.default_is_url} + brand={this.props.config.brand} customHsUrl={this.getCurrentHsUrl()} customIsUrl={this.getCurrentIsUrl()} registrationUrl={this.props.registrationUrl} diff --git a/src/components/structures/login/Registration.js b/src/components/structures/login/Registration.js index d852991b9c..e1b3780435 100644 --- a/src/components/structures/login/Registration.js +++ b/src/components/structures/login/Registration.js @@ -40,6 +40,7 @@ module.exports = React.createClass({ customIsUrl: React.PropTypes.string, defaultHsUrl: React.PropTypes.string, defaultIsUrl: React.PropTypes.string, + brand: React.PropTypes.string, email: React.PropTypes.string, username: React.PropTypes.string, guestAccessToken: React.PropTypes.string, @@ -66,6 +67,7 @@ module.exports = React.createClass({ this.registerLogic.setRegistrationUrl(this.props.registrationUrl); this.registerLogic.setIdSid(this.props.idSid); this.registerLogic.setGuestAccessToken(this.props.guestAccessToken); + this.registerLogic.setBrand(this.props.brand); this.registerLogic.recheckState(); }, From 1d314631403500c6df17ab57292f9218d4e00ac5 Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Thu, 2 Jun 2016 13:14:52 +0100 Subject: [PATCH 10/47] set email branding after registration --- src/Signup.js | 7 +----- src/UserSettingsStore.js | 4 ++-- .../structures/login/Registration.js | 22 ++++++++++++++++++- 3 files changed, 24 insertions(+), 9 deletions(-) diff --git a/src/Signup.js b/src/Signup.js index c4fce88262..4518955d95 100644 --- a/src/Signup.js +++ b/src/Signup.js @@ -51,7 +51,6 @@ class Register extends Signup { this.username = undefined; // desired this.email = undefined; // desired this.password = undefined; // desired - this.brand = undefined; // optional brand to let the HS brand its mail notifs } setClientSecret(secret) { @@ -74,10 +73,6 @@ class Register extends Signup { this.guestAccessToken = token; } - setBrand(brand) { - this.brand = brand; - } - getStep() { return this._step; } @@ -136,7 +131,7 @@ class Register extends Signup { return MatrixClientPeg.get().register( this.username, this.password, this.params.sessionId, authDict, bindEmail, - this.guestAccessToken, this.brand + this.guestAccessToken ).then(function(result) { self.credentials = result; self.setStep("COMPLETE"); diff --git a/src/UserSettingsStore.js b/src/UserSettingsStore.js index cf7131eb7b..9bb1388e76 100644 --- a/src/UserSettingsStore.js +++ b/src/UserSettingsStore.js @@ -100,7 +100,7 @@ module.exports = { return this.getEmailPusher(pushers, address) !== undefined; }, - addEmailPusher: function(address) { + addEmailPusher: function(address, data) { return MatrixClientPeg.get().setPusher({ kind: 'email', app_id: "m.email", @@ -108,7 +108,7 @@ module.exports = { app_display_name: 'Email Notifications', device_display_name: address, 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 }); }, diff --git a/src/components/structures/login/Registration.js b/src/components/structures/login/Registration.js index e1b3780435..2f15a3b5df 100644 --- a/src/components/structures/login/Registration.js +++ b/src/components/structures/login/Registration.js @@ -22,6 +22,7 @@ var sdk = require('../../../index'); var dis = require('../../../dispatcher'); var Signup = require("../../../Signup"); var ServerConfig = require("../../views/login/ServerConfig"); +var MatrixClientPeg = require("../../../MatrixClientPeg"); var RegistrationForm = require("../../views/login/RegistrationForm"); var CaptchaForm = require("../../views/login/CaptchaForm"); @@ -67,7 +68,6 @@ module.exports = React.createClass({ this.registerLogic.setRegistrationUrl(this.props.registrationUrl); this.registerLogic.setIdSid(this.props.idSid); this.registerLogic.setGuestAccessToken(this.props.guestAccessToken); - this.registerLogic.setBrand(this.props.brand); this.registerLogic.recheckState(); }, @@ -147,6 +147,26 @@ module.exports = React.createClass({ identityServerUrl: self.registerLogic.getIdentityServerUrl(), 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) { if (err.message) { self.setState({ From d812c77fc136cca79012ad576a3957767cee1b74 Mon Sep 17 00:00:00 2001 From: Richard van der Hoff Date: Thu, 2 Jun 2016 13:36:45 +0100 Subject: [PATCH 11/47] Bump to js-sdk 0.5.3 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 3f4a862f6f..b771cef20b 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,7 @@ "highlight.js": "^8.9.1", "linkifyjs": "^2.0.0-beta.4", "marked": "^0.3.5", - "matrix-js-sdk": "^0.5.2", + "matrix-js-sdk": "^0.5.3", "optimist": "^0.6.1", "q": "^1.4.1", "react": "^15.0.1", From 9a77796d770f5bee901697aabd792ad9b497ee3e Mon Sep 17 00:00:00 2001 From: Richard van der Hoff Date: Thu, 2 Jun 2016 13:38:18 +0100 Subject: [PATCH 12/47] Prepare changelog for v0.6.0 --- CHANGELOG.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 262d55c6da..6a7040627c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,22 @@ +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) =================================================================================================== [Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v0.5.1...v0.5.2) From 268cedee0f20759bb76ba4ddba02c358fecc536f Mon Sep 17 00:00:00 2001 From: Richard van der Hoff Date: Thu, 2 Jun 2016 13:38:19 +0100 Subject: [PATCH 13/47] 0.6.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index b771cef20b..93cbe60754 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "matrix-react-sdk", - "version": "0.5.2", + "version": "0.6.0", "description": "SDK for matrix.org using React", "author": "matrix.org", "repository": { From 5379956b0d1f33846d98b6e6540d28f8b51c139f Mon Sep 17 00:00:00 2001 From: Richard van der Hoff Date: Thu, 2 Jun 2016 16:42:19 +0100 Subject: [PATCH 14/47] jenkins.sh: remove spurious 'npm install' Just let npm install matrix-js-sdk, rather than installing the one from jenkins --- jenkins.sh | 3 --- 1 file changed, 3 deletions(-) diff --git a/jenkins.sh b/jenkins.sh index 51fab5d020..eeb7d7d56e 100755 --- a/jenkins.sh +++ b/jenkins.sh @@ -8,9 +8,6 @@ nvm use 4 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 npm install From 487f3c72dde7641d56d243af2e891982e1f45b7c Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Thu, 2 Jun 2016 16:59:38 +0100 Subject: [PATCH 15/47] fix new 3pid invite UI --- .../views/rooms/SearchableEntityList.js | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/src/components/views/rooms/SearchableEntityList.js b/src/components/views/rooms/SearchableEntityList.js index 28e7b1785c..a22126025c 100644 --- a/src/components/views/rooms/SearchableEntityList.js +++ b/src/components/views/rooms/SearchableEntityList.js @@ -129,14 +129,26 @@ var SearchableEntityList = React.createClass({ render: function() { var inputBox; - + if (this.props.showInputBox) { inputBox = (
{ this.setState({ focused: true }) } } - onBlur={ ()=>{ this.setState({ focused: false }) } } + 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} />
); From d857859d51cba7be1bc992bf16352feb71f02426 Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Thu, 2 Jun 2016 18:32:50 +0100 Subject: [PATCH 16/47] Prepare changelog for v0.6.1 --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6a7040627c..31d997c4a9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +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) From 204437e40c97a888ed58c66d10f77296aca24844 Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Thu, 2 Jun 2016 18:32:50 +0100 Subject: [PATCH 17/47] 0.6.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 93cbe60754..e9fbe742fa 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "matrix-react-sdk", - "version": "0.6.0", + "version": "0.6.1", "description": "SDK for matrix.org using React", "author": "matrix.org", "repository": { From 168f74d6cf3f444a13f4a1a0c59aeb0e5031069f Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Thu, 2 Jun 2016 18:53:50 +0100 Subject: [PATCH 18/47] correctly bump dep on js-sdk 0.5.4 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e9fbe742fa..daf691e33e 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,7 @@ "highlight.js": "^8.9.1", "linkifyjs": "^2.0.0-beta.4", "marked": "^0.3.5", - "matrix-js-sdk": "^0.5.3", + "matrix-js-sdk": "^0.5.4", "optimist": "^0.6.1", "q": "^1.4.1", "react": "^15.0.1", From 5ecbd10d361ff50eacde4faeeee24f22d078d88c Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Thu, 2 Jun 2016 18:55:34 +0100 Subject: [PATCH 19/47] Prepare changelog for v0.6.2 --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 31d997c4a9..2026ea2701 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +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) From b06ab78a81991cfba58a09dbde10b83135fe8c7c Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Thu, 2 Jun 2016 18:55:34 +0100 Subject: [PATCH 20/47] 0.6.2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index daf691e33e..4740a72894 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "matrix-react-sdk", - "version": "0.6.1", + "version": "0.6.2", "description": "SDK for matrix.org using React", "author": "matrix.org", "repository": { From e20a1acb883d8c6de28c623014605886a7c939b0 Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Thu, 2 Jun 2016 19:22:11 +0100 Subject: [PATCH 21/47] label our versions sensibly --- src/components/structures/UserSettings.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/components/structures/UserSettings.js b/src/components/structures/UserSettings.js index a0c03731da..e56e5d9d87 100644 --- a/src/components/structures/UserSettings.js +++ b/src/components/structures/UserSettings.js @@ -403,9 +403,8 @@ module.exports = React.createClass({ Identity Server is { MatrixClientPeg.get().getIdentityServerUrl() }
- Version {this.state.clientVersion} -
- {this.props.version} + matrix-react-sdk version: {this.state.clientVersion}
+ vector-web version: {this.props.version}
From 532e93d7cb910e9f357e7e7ddf6eb181a6ea02e3 Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Thu, 2 Jun 2016 21:07:04 +0100 Subject: [PATCH 22/47] fix up trailing whitespace and put the buttons in the right order --- src/components/views/dialogs/TextInputDialog.js | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/components/views/dialogs/TextInputDialog.js b/src/components/views/dialogs/TextInputDialog.js index d81ae98718..fed7ff079a 100644 --- a/src/components/views/dialogs/TextInputDialog.js +++ b/src/components/views/dialogs/TextInputDialog.js @@ -39,11 +39,11 @@ module.exports = React.createClass({ focus: true }; }, - + componentDidMount: function() { if (this.props.focus) { - // Set the cursor at the end of the text input - this.refs.textinput.value = this.props.value; + // Set the cursor at the end of the text input + this.refs.textinput.value = this.props.value; } }, @@ -83,13 +83,12 @@ module.exports = React.createClass({
- - +
); From 1b3c8481f6ceaaf51328c6f383f9ce98e45bdd21 Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Thu, 2 Jun 2016 21:07:47 +0100 Subject: [PATCH 23/47] fix dialog prompt an embarassing bug where if 3pid invites triggered a dialog they'd get wedged --- src/components/views/rooms/MemberList.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/components/views/rooms/MemberList.js b/src/components/views/rooms/MemberList.js index 21c0827fcc..328f9774c7 100644 --- a/src/components/views/rooms/MemberList.js +++ b/src/components/views/rooms/MemberList.js @@ -170,12 +170,16 @@ module.exports = React.createClass({ var TextInputDialog = sdk.getComponent("dialogs.TextInputDialog"); Modal.createDialog(TextInputDialog, { title: "Invite members by email", - description: "Please enter the email addresses to be invited (comma separated)", + description: "Please enter one or more email addresses", value: inputText, button: "Invite", onFinished: (should_invite, addresses)=>{ if (should_invite) { - this.onInvite(addresses); + // 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); } } }); From f8b3128645b58606ec2d729ab9dc9f10e74933f5 Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Thu, 2 Jun 2016 23:33:55 +0100 Subject: [PATCH 24/47] invite input box wording for amandine --- src/components/views/rooms/InviteMemberList.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/rooms/InviteMemberList.js b/src/components/views/rooms/InviteMemberList.js index 8f8b22eaa7..5246e2e54d 100644 --- a/src/components/views/rooms/InviteMemberList.js +++ b/src/components/views/rooms/InviteMemberList.js @@ -100,7 +100,7 @@ module.exports = React.createClass({ } return ( - Date: Fri, 3 Jun 2016 12:17:45 +0100 Subject: [PATCH 25/47] Prepare changelog for v0.6.3 --- CHANGELOG.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2026ea2701..70f946d7cc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,11 @@ +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) From 507f5e2ca19156a2afd3470fc9b17fb5e65cdf9b Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Fri, 3 Jun 2016 12:17:46 +0100 Subject: [PATCH 26/47] 0.6.3 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 4740a72894..7dce6b3d79 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "matrix-react-sdk", - "version": "0.6.2", + "version": "0.6.3", "description": "SDK for matrix.org using React", "author": "matrix.org", "repository": { From d5a522fdfe7e79b88d7f2ecc54ee4a81877a6c4f Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 7 Jun 2016 18:22:01 +0100 Subject: [PATCH 27/47] Second attempt at fixing the Velocity memory leak 1) Correct fix for Velociraptor (we need to find the DOM node and pass that in) 2) Do the same leak fix for the read marker 3) Update the dependency to our fork which is fixed to make the call we do to release memory actually work. 4) Remove the velocity-ui-pack dependency which is unnecessary because velocity-ui is included in the velocity package --- package.json | 3 +-- src/Velociraptor.js | 3 ++- src/components/structures/MessagePanel.js | 17 ++++++++++++++--- src/components/views/login/RegistrationForm.js | 2 +- 4 files changed, 18 insertions(+), 7 deletions(-) diff --git a/package.json b/package.json index 7dce6b3d79..6bea06f075 100644 --- a/package.json +++ b/package.json @@ -37,8 +37,7 @@ "react-dom": "^15.0.1", "react-gemini-scrollbar": "matrix-org/react-gemini-scrollbar#c3d942e", "sanitize-html": "^1.11.1", - "velocity-animate": "^1.2.3", - "velocity-ui-pack": "^1.2.2" + "velocity-animate": "vector-im/velocity#a70ebc7" }, "//babelversion": [ "brief experiments with babel6 seems to show that it generates source ", diff --git a/src/Velociraptor.js b/src/Velociraptor.js index 0abf34b230..2a8453c7ea 100644 --- a/src/Velociraptor.js +++ b/src/Velociraptor.js @@ -117,7 +117,8 @@ module.exports = React.createClass({ // 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]); + var domNode = ReactDom.findDOMNode(this.nodes[k]); + Velocity.Utilities.removeData(domNode); } this.nodes[k] = node; }, diff --git a/src/components/structures/MessagePanel.js b/src/components/structures/MessagePanel.js index 16b4892bc0..af047bd190 100644 --- a/src/components/structures/MessagePanel.js +++ b/src/components/structures/MessagePanel.js @@ -86,6 +86,10 @@ module.exports = React.createClass({ // to manage its animations this._readReceiptMap = {}; + // Remember the read marker ghost node so we can do the cleanup that + // Velocity requires + this.readMarkerGhostNode = null; + this._isMounted = true; }, @@ -422,9 +426,16 @@ module.exports = React.createClass({ }, _startAnimation: function(ghostNode) { - Velocity(ghostNode, {opacity: '0', width: '10%'}, - {duration: 400, easing: 'easeInSine', - delay: 1000}); + if (this.readMarkerGhostNode) { + Velocity.Utilities.removeData(this.readMarkerGhostNode); + } + this.readMarkerGhostNode = ghostNode; + + if (ghostNode) { + Velocity(ghostNode, {opacity: '0', width: '10%'}, + {duration: 400, easing: 'easeInSine', + delay: 1000}); + } }, _getReadMarkerGhostTile: function() { diff --git a/src/components/views/login/RegistrationForm.js b/src/components/views/login/RegistrationForm.js index 83bd1ab17c..ecc02c40d2 100644 --- a/src/components/views/login/RegistrationForm.js +++ b/src/components/views/login/RegistrationForm.js @@ -18,7 +18,7 @@ limitations under the License. var React = require('react'); var Velocity = require('velocity-animate'); -require('velocity-ui-pack'); +require('velocity-animate/velocity.ui'); var sdk = require('../../../index'); var Email = require('../../../email'); var Modal = require("../../../Modal"); From 943d7e18e16fabaa1104fefcf57ca90cb12de8b7 Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 7 Jun 2016 19:55:24 +0100 Subject: [PATCH 28/47] Add _ to internal member --- src/components/structures/MessagePanel.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/structures/MessagePanel.js b/src/components/structures/MessagePanel.js index af047bd190..c8e878118b 100644 --- a/src/components/structures/MessagePanel.js +++ b/src/components/structures/MessagePanel.js @@ -88,7 +88,7 @@ module.exports = React.createClass({ // Remember the read marker ghost node so we can do the cleanup that // Velocity requires - this.readMarkerGhostNode = null; + this._readMarkerGhostNode = null; this._isMounted = true; }, @@ -426,10 +426,10 @@ module.exports = React.createClass({ }, _startAnimation: function(ghostNode) { - if (this.readMarkerGhostNode) { - Velocity.Utilities.removeData(this.readMarkerGhostNode); + if (this._readMarkerGhostNode) { + Velocity.Utilities.removeData(this._readMarkerGhostNode); } - this.readMarkerGhostNode = ghostNode; + this._readMarkerGhostNode = ghostNode; if (ghostNode) { Velocity(ghostNode, {opacity: '0', width: '10%'}, From 8679ce80c81f673c54ba3a5e9ad861927a1e2675 Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 7 Jun 2016 20:28:38 +0100 Subject: [PATCH 29/47] Update vector velocity fork package name --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 6bea06f075..76cb824483 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,7 @@ "react-dom": "^15.0.1", "react-gemini-scrollbar": "matrix-org/react-gemini-scrollbar#c3d942e", "sanitize-html": "^1.11.1", - "velocity-animate": "vector-im/velocity#a70ebc7" + "velocity-vector": "vector-im/velocity#a70ebc7" }, "//babelversion": [ "brief experiments with babel6 seems to show that it generates source ", From aca0e060a47685720fd1a7c73189f05352738b03 Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 7 Jun 2016 20:47:37 +0100 Subject: [PATCH 30/47] Also change velocity-vector commit & requires Because it needs a versiob where the package name is actually updated and the requires need to use the right package name --- package.json | 2 +- src/Velociraptor.js | 2 +- src/VelocityBounce.js | 2 +- src/components/views/login/RegistrationForm.js | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 76cb824483..f5972473dd 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,7 @@ "react-dom": "^15.0.1", "react-gemini-scrollbar": "matrix-org/react-gemini-scrollbar#c3d942e", "sanitize-html": "^1.11.1", - "velocity-vector": "vector-im/velocity#a70ebc7" + "velocity-vector": "vector-im/velocity#059e3b2" }, "//babelversion": [ "brief experiments with babel6 seems to show that it generates source ", diff --git a/src/Velociraptor.js b/src/Velociraptor.js index 2a8453c7ea..f45925867f 100644 --- a/src/Velociraptor.js +++ b/src/Velociraptor.js @@ -1,6 +1,6 @@ var React = require('react'); var ReactDom = require('react-dom'); -var Velocity = require('velocity-animate'); +var Velocity = require('velocity-vector'); /** * The Velociraptor contains components and animates transitions with velocity. diff --git a/src/VelocityBounce.js b/src/VelocityBounce.js index c85aa254fa..168b0b14af 100644 --- a/src/VelocityBounce.js +++ b/src/VelocityBounce.js @@ -1,4 +1,4 @@ -var Velocity = require('velocity-animate'); +var Velocity = require('velocity-vector'); // courtesy of https://github.com/julianshapiro/velocity/issues/283 // We only use easeOutBounce (easeInBounce is just sort of nonsensical) diff --git a/src/components/views/login/RegistrationForm.js b/src/components/views/login/RegistrationForm.js index ecc02c40d2..a172d77bb4 100644 --- a/src/components/views/login/RegistrationForm.js +++ b/src/components/views/login/RegistrationForm.js @@ -17,8 +17,8 @@ limitations under the License. 'use strict'; var React = require('react'); -var Velocity = require('velocity-animate'); -require('velocity-animate/velocity.ui'); +var Velocity = require('velocity-vector'); +require('velocity-vector/velocity.ui'); var sdk = require('../../../index'); var Email = require('../../../email'); var Modal = require("../../../Modal"); From b54376aa2acda9451d684a1565772503beaff631 Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 7 Jun 2016 21:49:06 +0100 Subject: [PATCH 31/47] Add singleton object to hold SDK configuration --- src/SdkConfig.js | 45 +++++++++++++++++++++++++ src/components/structures/MatrixChat.js | 2 ++ 2 files changed, 47 insertions(+) create mode 100644 src/SdkConfig.js diff --git a/src/SdkConfig.js b/src/SdkConfig.js new file mode 100644 index 0000000000..1452aaa64b --- /dev/null +++ b/src/SdkConfig.js @@ -0,0 +1,45 @@ +/* +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 DEFAULTS = { + // URL to a page we show in an iframe to configure integrations + integrations_ui_url: "https://scalar.vector.im/", + // Base URL to the REST interface of the integrations server + integrations_rest_url: "https://scalar.vector.im/api", +}; + +class SdkConfig { + + static get() { + return global.mxReactSdkConfig; + } + + static put(cfg) { + var defaultKeys = Object.keys(DEFAULTS); + for (var i = 0; i < defaultKeys.length; ++i) { + if (cfg[defaultKeys[i]] === undefined) { + cfg[defaultKeys[i]] = DEFAULTS[defaultKeys[i]]; + } + } + global.mxReactSdkConfig = cfg; + } + + static unset() { + global.mxReactSdkConfig = undefined; + } +} + +module.exports = SdkConfig; diff --git a/src/components/structures/MatrixChat.js b/src/components/structures/MatrixChat.js index f708a3e0fb..9aad4e72de 100644 --- a/src/components/structures/MatrixChat.js +++ b/src/components/structures/MatrixChat.js @@ -19,6 +19,7 @@ var url = require('url'); var Favico = require('favico.js'); var MatrixClientPeg = require("../../MatrixClientPeg"); +var SdkConfig = require("../../SdkConfig"); var Notifier = require("../../Notifier"); var ContextualMenu = require("../../ContextualMenu"); var RoomListSorter = require("../../RoomListSorter"); @@ -123,6 +124,7 @@ module.exports = React.createClass({ }, componentWillMount: function() { + SdkConfig.put(this.props.config); this.favicon = new Favico({animation: 'none'}); }, From 33251e65666c4619c8551c515d85ca28d161a112 Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 7 Jun 2016 22:03:31 +0100 Subject: [PATCH 32/47] Remove default options that shouldn't be part of this PR --- src/SdkConfig.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/SdkConfig.js b/src/SdkConfig.js index 1452aaa64b..ca0b7455d8 100644 --- a/src/SdkConfig.js +++ b/src/SdkConfig.js @@ -15,10 +15,6 @@ limitations under the License. */ var DEFAULTS = { - // URL to a page we show in an iframe to configure integrations - integrations_ui_url: "https://scalar.vector.im/", - // Base URL to the REST interface of the integrations server - integrations_rest_url: "https://scalar.vector.im/api", }; class SdkConfig { From d063735c9669a8bdffeeeabff763d66a80aa5c6c Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 7 Jun 2016 22:07:23 +0100 Subject: [PATCH 33/47] Use variable for key name --- src/SdkConfig.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/SdkConfig.js b/src/SdkConfig.js index ca0b7455d8..9eb1baf8c2 100644 --- a/src/SdkConfig.js +++ b/src/SdkConfig.js @@ -26,8 +26,9 @@ class SdkConfig { static put(cfg) { var defaultKeys = Object.keys(DEFAULTS); for (var i = 0; i < defaultKeys.length; ++i) { - if (cfg[defaultKeys[i]] === undefined) { - cfg[defaultKeys[i]] = DEFAULTS[defaultKeys[i]]; + var key = defaultKeys[i]; + if (cfg[key] === undefined) { + cfg[key] = DEFAULTS[key]; } } global.mxReactSdkConfig = cfg; From 12816c5e1f05d1848b823d46f454a05c7b3f466a Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 7 Jun 2016 22:14:56 +0100 Subject: [PATCH 34/47] Explanatory comment --- src/SdkConfig.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/SdkConfig.js b/src/SdkConfig.js index 9eb1baf8c2..11c1dd5858 100644 --- a/src/SdkConfig.js +++ b/src/SdkConfig.js @@ -17,6 +17,12 @@ limitations under the License. var DEFAULTS = { }; +/** + * Class that stores an instance of the SDK config dictionary + * as a singleton such that it can be accessed from SDK + * components without having to be passed deep though + * multiple levels of React components. + */ class SdkConfig { static get() { From 7e8c8dd336f7d83307584f14bd90794961f859cc Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 8 Jun 2016 14:54:34 +0100 Subject: [PATCH 35/47] Remove SdkConfig just take a config in MatrixChat, pass the individual things down through the chain. This may become unwieldy: let's see how it goes. --- src/SdkConfig.js | 48 ----------------------- src/components/structures/MatrixChat.js | 4 +- src/components/structures/UserSettings.js | 6 ++- 3 files changed, 5 insertions(+), 53 deletions(-) delete mode 100644 src/SdkConfig.js diff --git a/src/SdkConfig.js b/src/SdkConfig.js deleted file mode 100644 index 11c1dd5858..0000000000 --- a/src/SdkConfig.js +++ /dev/null @@ -1,48 +0,0 @@ -/* -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 DEFAULTS = { -}; - -/** - * Class that stores an instance of the SDK config dictionary - * as a singleton such that it can be accessed from SDK - * components without having to be passed deep though - * multiple levels of React components. - */ -class SdkConfig { - - static get() { - return global.mxReactSdkConfig; - } - - static put(cfg) { - var defaultKeys = Object.keys(DEFAULTS); - for (var i = 0; i < defaultKeys.length; ++i) { - var key = defaultKeys[i]; - if (cfg[key] === undefined) { - cfg[key] = DEFAULTS[key]; - } - } - global.mxReactSdkConfig = cfg; - } - - static unset() { - global.mxReactSdkConfig = undefined; - } -} - -module.exports = SdkConfig; diff --git a/src/components/structures/MatrixChat.js b/src/components/structures/MatrixChat.js index 9aad4e72de..7f7bf034b8 100644 --- a/src/components/structures/MatrixChat.js +++ b/src/components/structures/MatrixChat.js @@ -19,7 +19,6 @@ var url = require('url'); var Favico = require('favico.js'); var MatrixClientPeg = require("../../MatrixClientPeg"); -var SdkConfig = require("../../SdkConfig"); var Notifier = require("../../Notifier"); var ContextualMenu = require("../../ContextualMenu"); var RoomListSorter = require("../../RoomListSorter"); @@ -124,7 +123,6 @@ module.exports = React.createClass({ }, componentWillMount: function() { - SdkConfig.put(this.props.config); this.favicon = new Favico({animation: 'none'}); }, @@ -1094,7 +1092,7 @@ module.exports = React.createClass({ right_panel = break; case this.PageTypes.UserSettings: - page_element = + page_element = right_panel = break; case this.PageTypes.CreateRoom: diff --git a/src/components/structures/UserSettings.js b/src/components/structures/UserSettings.js index e56e5d9d87..3face4649c 100644 --- a/src/components/structures/UserSettings.js +++ b/src/components/structures/UserSettings.js @@ -31,7 +31,9 @@ module.exports = React.createClass({ propTypes: { 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() { @@ -333,7 +335,7 @@ module.exports = React.createClass({

Notifications

- +
); } From 086698cd113e6988b539f57a914c7e65a97791f6 Mon Sep 17 00:00:00 2001 From: Richard van der Hoff Date: Wed, 8 Jun 2016 13:09:07 +0100 Subject: [PATCH 36/47] Add device info to user settings Requires e81ce23 from matrix-js-sdk --- src/components/structures/UserSettings.js | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/components/structures/UserSettings.js b/src/components/structures/UserSettings.js index e56e5d9d87..9bdde0dde4 100644 --- a/src/components/structures/UserSettings.js +++ b/src/components/structures/UserSettings.js @@ -244,6 +244,23 @@ module.exports = React.createClass({ }); }, + _renderDeviceInfo: function() { + var client = MatrixClientPeg.get(); + var deviceId = client.deviceId; + var olmKey = client.getDeviceEd25519Key() || ""; + return ( +
+

Cryptography

+
+
    +
  • Device ID: {deviceId}
  • +
  • Device key: {olmKey}
  • +
+
+
+ ); + }, + render: function() { var self = this; var Loader = sdk.getComponent("elements.Spinner"); @@ -390,6 +407,8 @@ module.exports = React.createClass({ {notification_area} + {this._renderDeviceInfo()} +

Advanced

From f3e66e6fd2cfed08be795f02ad8894437feb6d1f Mon Sep 17 00:00:00 2001 From: Richard van der Hoff Date: Wed, 8 Jun 2016 13:13:41 +0100 Subject: [PATCH 37/47] Add device info to member info Requires e81ce23 in matrix-js-sdk --- src/component-index.js | 1 + .../views/rooms/MemberDeviceInfo.js | 58 +++++++++ src/components/views/rooms/MemberInfo.js | 115 ++++++++++++++---- 3 files changed, 152 insertions(+), 22 deletions(-) create mode 100644 src/components/views/rooms/MemberDeviceInfo.js diff --git a/src/component-index.js b/src/component-index.js index 967cc5d685..3570523bde 100644 --- a/src/component-index.js +++ b/src/component-index.js @@ -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.InviteMemberList'] = require('./components/views/rooms/InviteMemberList'); 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.MemberList'] = require('./components/views/rooms/MemberList'); module.exports.components['views.rooms.MemberTile'] = require('./components/views/rooms/MemberTile'); diff --git a/src/components/views/rooms/MemberDeviceInfo.js b/src/components/views/rooms/MemberDeviceInfo.js new file mode 100644 index 0000000000..6af7b848c0 --- /dev/null +++ b/src/components/views/rooms/MemberDeviceInfo.js @@ -0,0 +1,58 @@ +/* +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); + // TODO: wire this up properly + this.props.device.verified = true; + this.forceUpdate(); + }, + + render: function() { + var indicator = null, button = null; + if (this.props.device.verified) { + indicator = ( +
+ ); + } else { + button = ( +
+ Verify +
+ ); + } + return ( +
+
{this.props.device.id}
+
{this.props.device.key}
+ {indicator} + {button} +
+ ); + }, +}); diff --git a/src/components/views/rooms/MemberInfo.js b/src/components/views/rooms/MemberInfo.js index ba4a3734f5..385f9dee19 100644 --- a/src/components/views/rooms/MemberInfo.js +++ b/src/components/views/rooms/MemberInfo.js @@ -34,23 +34,81 @@ var sdk = require('../../../index'); module.exports = React.createClass({ displayName: 'MemberInfo', + propTypes: { + member: React.PropTypes.object.isRequired, + onFinished: React.PropTypes.func, + }, + getDefaultProps: function() { return { onFinished: function() {} }; }, - componentDidMount: function() { - // work out the current state - if (this.props.member) { - var memberState = this._calculateOpsPermissions(this.props.member); - this.setState(memberState); + getInitialState: function() { + return { + can: { + kick: false, + ban: false, + mute: false, + modifyLevel: false + }, + muted: false, + isTargetMod: false, + updating: 0, + devices: null, // null means device list is loading } }, + + componentWillMount: function() { + this._cancelDeviceList = null; + }, + + componentDidMount: function() { + this._updateStateForNewMember(this.props.member); + }, + componentWillReceiveProps: function(newProps) { - var memberState = this._calculateOpsPermissions(newProps.member); - this.setState(memberState); + if (this.props.member.userId != newProps.member.userId) { + this._updateStateForNewMember(newProps.member); + } + }, + + componentWillUnmount: function() { + if (this._cancelDeviceList) { + this._cancelDeviceList(); + } + }, + + _updateStateForNewMember: function(member) { + var newState = this._calculateOpsPermissions(member); + 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).done(function() { + if (cancelled) { + // we got cancelled - presumably a different user now + return; + } + self._cancelDeviceList = null; + var devices = client.listDeviceKeys(member.userId); + self.setState({devices: devices}); + }); }, onKick: function() { @@ -371,20 +429,6 @@ module.exports = React.createClass({ this.props.onFinished(); }, - getInitialState: function() { - return { - can: { - kick: false, - ban: false, - mute: false, - modifyLevel: false - }, - muted: false, - isTargetMod: false, - updating: 0, - } - }, - _calculateOpsPermissions: function(member) { var defaultPerms = { can: {}, @@ -476,6 +520,32 @@ module.exports = React.createClass({ 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 (devices === null) { + // still loading + devComponents = ; + } else { + devComponents = []; + for (var i = 0; i < devices.length; i++) { + devComponents.push(); + } + } + + return ( +
+

Devices

+ {devComponents} +
+ ); + }, + render: function() { var startChat, kickButton, banButton, muteButton, giveModButton, spinner; if (this.props.member.userId !== MatrixClientPeg.get().credentials.userId) { @@ -552,6 +622,8 @@ module.exports = React.createClass({ { startChat } + { this._renderDevices() } + { adminTools } { spinner } @@ -559,4 +631,3 @@ module.exports = React.createClass({ ); } }); - From 1616431d27a6dbc6878c2f710a2d8f597c1446b5 Mon Sep 17 00:00:00 2001 From: Richard van der Hoff Date: Wed, 8 Jun 2016 17:01:13 +0100 Subject: [PATCH 38/47] EventTile: add classes to indicate verifiedness Add a couple of CSS classes to event tiles to reflect whether encrypted events have been verified or not. --- src/components/views/rooms/EventTile.js | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/src/components/views/rooms/EventTile.js b/src/components/views/rooms/EventTile.js index 7db8af9312..13534866c5 100644 --- a/src/components/views/rooms/EventTile.js +++ b/src/components/views/rooms/EventTile.js @@ -128,18 +128,25 @@ module.exports = React.createClass({ }, getInitialState: function() { - return {menu: false, allReadAvatars: false}; + return {menu: false, allReadAvatars: false, verified: null}; }, componentWillMount: function() { // don't do RR animations until we are mounted this._suppressReadReceiptAnimation = true; + this._verifyEvent(this.props.mxEvent); }, componentDidMount: function() { this._suppressReadReceiptAnimation = false; }, + componentWillReceiveProps: function (nextProps) { + if (nextProps.mxEvent !== this.props.mxEvent) { + this._verifyEvent(nextProps.mxEvent); + } + }, + shouldComponentUpdate: function (nextProps, nextState) { if (!ObjectUtils.shallowEqual(this.state, nextState)) { return true; @@ -152,6 +159,18 @@ module.exports = React.createClass({ return false; }, + _verifyEvent: function(mxEvent) { + var verified = null; + + if (mxEvent.isEncrypted()) { + verified = MatrixClientPeg.get().isEventSenderVerified(mxEvent); + } + + this.setState({ + verified: verified + }); + }, + _propsEqual: function(objA, objB) { var keysA = Object.keys(objA); var keysB = Object.keys(objB); @@ -346,6 +365,8 @@ module.exports = React.createClass({ mx_EventTile_last: this.props.last, mx_EventTile_contextual: this.props.contextual, menu: this.state.menu, + mx_EventTile_verified: this.state.verified == true, + mx_EventTile_unverified: this.state.verified == false, }); var timestamp = From 7ce49c752f68c582f0d7d19ed19b9fec64486346 Mon Sep 17 00:00:00 2001 From: Richard van der Hoff Date: Wed, 8 Jun 2016 18:35:43 +0100 Subject: [PATCH 39/47] Wire up events to update UI on device verification Use the dispatcher to update event tiles and memberdeviceinfo when a device is marked as verified. --- src/components/views/rooms/EventTile.js | 15 ++++++++++++++ .../views/rooms/MemberDeviceInfo.js | 13 +++++++++--- src/components/views/rooms/MemberInfo.js | 20 +++++++++++++++++++ 3 files changed, 45 insertions(+), 3 deletions(-) diff --git a/src/components/views/rooms/EventTile.js b/src/components/views/rooms/EventTile.js index 13534866c5..e1390ced6a 100644 --- a/src/components/views/rooms/EventTile.js +++ b/src/components/views/rooms/EventTile.js @@ -139,6 +139,7 @@ module.exports = React.createClass({ componentDidMount: function() { this._suppressReadReceiptAnimation = false; + this.dispatcherRef = dispatcher.register(this.onAction); }, componentWillReceiveProps: function (nextProps) { @@ -159,6 +160,20 @@ module.exports = React.createClass({ return false; }, + componentWillUnmount: function() { + dispatcher.unregister(this.dispatcherRef); + }, + + onAction: function(payload) { + switch (payload.action) { + case 'device_verified': + if (payload.params.userId == this.props.mxEvent.getSender()) { + this._verifyEvent(this.props.mxEvent); + } + break; + } + }, + _verifyEvent: function(mxEvent) { var verified = null; diff --git a/src/components/views/rooms/MemberDeviceInfo.js b/src/components/views/rooms/MemberDeviceInfo.js index 6af7b848c0..296e0b4720 100644 --- a/src/components/views/rooms/MemberDeviceInfo.js +++ b/src/components/views/rooms/MemberDeviceInfo.js @@ -17,6 +17,8 @@ limitations under the License. var React = require('react'); var MatrixClientPeg = require("../../../MatrixClientPeg"); +var dis = require("../../../dispatcher"); + module.exports = React.createClass({ displayName: 'MemberDeviceInfo', propTypes: { @@ -27,9 +29,14 @@ module.exports = React.createClass({ onVerifyClick: function() { MatrixClientPeg.get().setDeviceVerified(this.props.userId, this.props.device.id); - // TODO: wire this up properly - this.props.device.verified = true; - this.forceUpdate(); + + dis.dispatch({ + action: 'device_verified', + params: { + userId: this.props.userId, + deviceId: this.props.device.id, + }, + }); }, render: function() { diff --git a/src/components/views/rooms/MemberInfo.js b/src/components/views/rooms/MemberInfo.js index 385f9dee19..2f68ebfb7d 100644 --- a/src/components/views/rooms/MemberInfo.js +++ b/src/components/views/rooms/MemberInfo.js @@ -67,6 +67,7 @@ module.exports = React.createClass({ componentDidMount: function() { this._updateStateForNewMember(this.props.member); + this.dispatcherRef = dis.register(this.onAction); }, componentWillReceiveProps: function(newProps) { @@ -76,11 +77,30 @@ module.exports = React.createClass({ }, componentWillUnmount: function() { + dis.unregister(this.dispatcherRef); if (this._cancelDeviceList) { this._cancelDeviceList(); } }, + onAction: function(payload) { + switch (payload.action) { + case 'device_verified': + if (payload.params.userId == this.props.member.userId) { + this._onDeviceVerified(); + } + break; + } + }, + + _onDeviceVerified: function() { + // no need to re-download the whole thing; just update our copy of the + // list. + var devices = MatrixClientPeg.get().listDeviceKeys( + this.props.member.userId); + this.setState({devices: devices}); + }, + _updateStateForNewMember: function(member) { var newState = this._calculateOpsPermissions(member); newState.devices = null; From c123f028422941656c9343444e2b7af5fb944a7d Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 8 Jun 2016 18:45:46 +0100 Subject: [PATCH 40/47] Make the config optional --- src/components/structures/MatrixChat.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/components/structures/MatrixChat.js b/src/components/structures/MatrixChat.js index 7f7bf034b8..9d0b383c0a 100644 --- a/src/components/structures/MatrixChat.js +++ b/src/components/structures/MatrixChat.js @@ -41,7 +41,7 @@ module.exports = React.createClass({ displayName: 'MatrixChat', propTypes: { - config: React.PropTypes.object.isRequired, + config: React.PropTypes.object, ConferenceHandler: React.PropTypes.any, onNewScreen: React.PropTypes.func, registrationUrl: React.PropTypes.string, @@ -84,7 +84,8 @@ module.exports = React.createClass({ getDefaultProps: function() { return { - startingQueryParams: {} + startingQueryParams: {}, + config: {}, }; }, From 85770feb31437419200cd89bde2b3f204570e2bb Mon Sep 17 00:00:00 2001 From: Richard van der Hoff Date: Wed, 8 Jun 2016 21:25:42 +0100 Subject: [PATCH 41/47] device verification: use a js-sdk event We'll probably want to be able to bubble up device verifications from the js-sdk at some point, so let's use a js-sdk event for this. --- src/components/views/rooms/EventTile.js | 17 ++++++----- .../views/rooms/MemberDeviceInfo.js | 10 ------- src/components/views/rooms/MemberInfo.js | 28 ++++++++----------- 3 files changed, 19 insertions(+), 36 deletions(-) diff --git a/src/components/views/rooms/EventTile.js b/src/components/views/rooms/EventTile.js index e1390ced6a..ff02139d87 100644 --- a/src/components/views/rooms/EventTile.js +++ b/src/components/views/rooms/EventTile.js @@ -139,7 +139,7 @@ module.exports = React.createClass({ componentDidMount: function() { this._suppressReadReceiptAnimation = false; - this.dispatcherRef = dispatcher.register(this.onAction); + MatrixClientPeg.get().on("deviceVerified", this.onDeviceVerified); }, componentWillReceiveProps: function (nextProps) { @@ -161,16 +161,15 @@ module.exports = React.createClass({ }, componentWillUnmount: function() { - dispatcher.unregister(this.dispatcherRef); + var client = MatrixClientPeg.get(); + if (client) { + client.removeListener("deviceVerified", this.onDeviceVerified); + } }, - onAction: function(payload) { - switch (payload.action) { - case 'device_verified': - if (payload.params.userId == this.props.mxEvent.getSender()) { - this._verifyEvent(this.props.mxEvent); - } - break; + onDeviceVerified: function(userId, device) { + if (userId == this.props.mxEvent.getSender()) { + this._verifyEvent(this.props.mxEvent); } }, diff --git a/src/components/views/rooms/MemberDeviceInfo.js b/src/components/views/rooms/MemberDeviceInfo.js index 296e0b4720..da53c744b2 100644 --- a/src/components/views/rooms/MemberDeviceInfo.js +++ b/src/components/views/rooms/MemberDeviceInfo.js @@ -17,8 +17,6 @@ limitations under the License. var React = require('react'); var MatrixClientPeg = require("../../../MatrixClientPeg"); -var dis = require("../../../dispatcher"); - module.exports = React.createClass({ displayName: 'MemberDeviceInfo', propTypes: { @@ -29,14 +27,6 @@ module.exports = React.createClass({ onVerifyClick: function() { MatrixClientPeg.get().setDeviceVerified(this.props.userId, this.props.device.id); - - dis.dispatch({ - action: 'device_verified', - params: { - userId: this.props.userId, - deviceId: this.props.device.id, - }, - }); }, render: function() { diff --git a/src/components/views/rooms/MemberInfo.js b/src/components/views/rooms/MemberInfo.js index 2f68ebfb7d..c50b6e919e 100644 --- a/src/components/views/rooms/MemberInfo.js +++ b/src/components/views/rooms/MemberInfo.js @@ -67,7 +67,7 @@ module.exports = React.createClass({ componentDidMount: function() { this._updateStateForNewMember(this.props.member); - this.dispatcherRef = dis.register(this.onAction); + MatrixClientPeg.get().on("deviceVerified", this.onDeviceVerified); }, componentWillReceiveProps: function(newProps) { @@ -77,30 +77,24 @@ module.exports = React.createClass({ }, componentWillUnmount: function() { - dis.unregister(this.dispatcherRef); + var client = MatrixClientPeg.get(); + if (client) { + client.removeListener("deviceVerified", this.onDeviceVerified); + } if (this._cancelDeviceList) { this._cancelDeviceList(); } }, - onAction: function(payload) { - switch (payload.action) { - case 'device_verified': - if (payload.params.userId == this.props.member.userId) { - this._onDeviceVerified(); - } - break; + 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}); } }, - _onDeviceVerified: function() { - // no need to re-download the whole thing; just update our copy of the - // list. - var devices = MatrixClientPeg.get().listDeviceKeys( - this.props.member.userId); - this.setState({devices: devices}); - }, - _updateStateForNewMember: function(member) { var newState = this._calculateOpsPermissions(member); newState.devices = null; From cab24bb14c528dbe09b56bbc92544710f3164b4c Mon Sep 17 00:00:00 2001 From: Richard van der Hoff Date: Wed, 8 Jun 2016 21:33:38 +0100 Subject: [PATCH 42/47] Switch matrix-js-sdk back to develop --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f5972473dd..4f1fcbb20c 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,7 @@ "highlight.js": "^8.9.1", "linkifyjs": "^2.0.0-beta.4", "marked": "^0.3.5", - "matrix-js-sdk": "^0.5.4", + "matrix-js-sdk": "matrix-org/matrix-js-sdk#develop", "optimist": "^0.6.1", "q": "^1.4.1", "react": "^15.0.1", From 05404efa344e6cad39a371b2aa567335954e7ab6 Mon Sep 17 00:00:00 2001 From: David Baker Date: Thu, 9 Jun 2016 09:53:37 +0100 Subject: [PATCH 43/47] Remove if (this.props.config) Since it now defaults to the empty dictionary (and if you pass null in, you deserve everything you get). --- src/components/structures/MatrixChat.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/structures/MatrixChat.js b/src/components/structures/MatrixChat.js index 9d0b383c0a..cca7d1fd9f 100644 --- a/src/components/structures/MatrixChat.js +++ b/src/components/structures/MatrixChat.js @@ -98,7 +98,7 @@ module.exports = React.createClass({ else if (window.localStorage && 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 "https://matrix.org"; @@ -117,7 +117,7 @@ module.exports = React.createClass({ else if (window.localStorage && 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 "https://matrix.org"; From bb405563c8c32a97d6b81a173f8a27afb4fba247 Mon Sep 17 00:00:00 2001 From: David Baker Date: Thu, 9 Jun 2016 10:54:56 +0100 Subject: [PATCH 44/47] Fix default servers --- src/components/structures/MatrixChat.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/components/structures/MatrixChat.js b/src/components/structures/MatrixChat.js index cca7d1fd9f..6c41380751 100644 --- a/src/components/structures/MatrixChat.js +++ b/src/components/structures/MatrixChat.js @@ -99,9 +99,8 @@ module.exports = React.createClass({ return window.localStorage.getItem("mx_hs_url"); } else { - return this.props.config.default_hs_url + return this.props.config.default_hs_url || "https://matrix.org"; } - return "https://matrix.org"; }, getFallbackHsUrl: function() { @@ -118,9 +117,8 @@ module.exports = React.createClass({ return window.localStorage.getItem("mx_is_url"); } else { - return this.props.config.default_is_url + return this.props.config.default_is_url || "https://matrix.org" } - return "https://matrix.org"; }, componentWillMount: function() { From 98ef793809a662f4864df75ade0856d0ce52ecd6 Mon Sep 17 00:00:00 2001 From: David Baker Date: Thu, 9 Jun 2016 10:56:14 +0100 Subject: [PATCH 45/47] Default IS should be vector.im now --- src/components/structures/MatrixChat.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/structures/MatrixChat.js b/src/components/structures/MatrixChat.js index 6c41380751..0f4a714fc1 100644 --- a/src/components/structures/MatrixChat.js +++ b/src/components/structures/MatrixChat.js @@ -117,7 +117,7 @@ module.exports = React.createClass({ return window.localStorage.getItem("mx_is_url"); } else { - return this.props.config.default_is_url || "https://matrix.org" + return this.props.config.default_is_url || "https://vector.im" } }, From 235266151366b34e42b12da1ebec75d15f4b0495 Mon Sep 17 00:00:00 2001 From: Richard van der Hoff Date: Wed, 8 Jun 2016 22:54:48 +0100 Subject: [PATCH 46/47] MemberInfo: remove spinner if device query fails If the request to /keys/query fails (for example, because we are a guest), we need to remove the spinner. --- src/components/views/rooms/MemberInfo.js | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/src/components/views/rooms/MemberInfo.js b/src/components/views/rooms/MemberInfo.js index c50b6e919e..bd9fd06be2 100644 --- a/src/components/views/rooms/MemberInfo.js +++ b/src/components/views/rooms/MemberInfo.js @@ -56,7 +56,8 @@ module.exports = React.createClass({ muted: false, isTargetMod: false, updating: 0, - devices: null, // null means device list is loading + devicesLoading: true, + devices: null, } }, @@ -97,6 +98,7 @@ module.exports = React.createClass({ _updateStateForNewMember: function(member) { var newState = this._calculateOpsPermissions(member); + newState.devicesLoading = true; newState.devices = null; this.setState(newState); @@ -114,14 +116,18 @@ module.exports = React.createClass({ var client = MatrixClientPeg.get(); var self = this; - client.downloadKeys([member.userId], true).done(function() { + client.downloadKeys([member.userId], true).finally(function() { + self._cancelDeviceList = null; + }).done(function() { if (cancelled) { // we got cancelled - presumably a different user now return; } - self._cancelDeviceList = null; var devices = client.listDeviceKeys(member.userId); - self.setState({devices: devices}); + self.setState({devicesLoading: false, devices: devices}); + }, function(err) { + console.log("Error downloading devices", err); + self.setState({devicesLoading: false}); }); }, @@ -540,9 +546,13 @@ module.exports = React.createClass({ var Spinner = sdk.getComponent("elements.Spinner"); var devComponents; - if (devices === null) { + if (this.state.devicesLoading) { // still loading devComponents = ; + } 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++) { From de36aa63fb61c9aee011221e2db6159fe1653ae9 Mon Sep 17 00:00:00 2001 From: Richard van der Hoff Date: Wed, 8 Jun 2016 23:03:46 +0100 Subject: [PATCH 47/47] Factor out common parts of room creation Take the duplicated code out of MatrixChat and MemberInfo, and put it in a separate 'createRoom' module --- src/components/structures/MatrixChat.js | 46 +------------ src/components/views/rooms/MemberInfo.js | 51 +++----------- src/createRoom.js | 86 ++++++++++++++++++++++++ 3 files changed, 97 insertions(+), 86 deletions(-) create mode 100644 src/createRoom.js diff --git a/src/components/structures/MatrixChat.js b/src/components/structures/MatrixChat.js index cca7d1fd9f..461410ae82 100644 --- a/src/components/structures/MatrixChat.js +++ b/src/components/structures/MatrixChat.js @@ -37,6 +37,8 @@ var MatrixTools = require('../../MatrixTools'); var linkifyMatrix = require("../../linkify-matrix"); var KeyCode = require('../../KeyCode'); +var createRoom = require("../../createRoom"); + module.exports = React.createClass({ displayName: 'MatrixChat', @@ -467,49 +469,7 @@ module.exports = React.createClass({ //this._setPage(this.PageTypes.CreateRoom); //this.notifyNewScreen('new'); - var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); - 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: '', - visibility: 'private', - } - ], - }).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() - }); - }); + createRoom().done(); break; case 'view_room_directory': this._setPage(this.PageTypes.RoomDirectory); diff --git a/src/components/views/rooms/MemberInfo.js b/src/components/views/rooms/MemberInfo.js index c50b6e919e..3cddc582c5 100644 --- a/src/components/views/rooms/MemberInfo.js +++ b/src/components/views/rooms/MemberInfo.js @@ -30,6 +30,7 @@ var MatrixClientPeg = require("../../../MatrixClientPeg"); var dis = require("../../../dispatcher"); var Modal = require("../../../Modal"); var sdk = require('../../../index'); +var createRoom = require('../../../createRoom'); module.exports = React.createClass({ displayName: 'MemberInfo', @@ -387,51 +388,15 @@ module.exports = React.createClass({ this.props.onFinished(); } 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 }); - MatrixClientPeg.get().createRoom({ - // XXX: FIXME: deduplicate this with "view_create_room" in MatrixChat - invite: [this.props.member.userId], - 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: '', - visibility: 'private', - } - ], - }).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(()=>{ + createRoom({ + createOpts: { + invite: [this.props.member.userId], + }, + }).finally(function() { + self.props.onFinished(); self.setState({ updating: self.state.updating - 1 }); - }); + }).done(); } }, diff --git a/src/createRoom.js b/src/createRoom.js new file mode 100644 index 0000000000..658561e78a --- /dev/null +++ b/src/createRoom.js @@ -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;