diff --git a/src/ContentMessages.js b/src/ContentMessages.js index 82c295756b..bbd714fa57 100644 --- a/src/ContentMessages.js +++ b/src/ContentMessages.js @@ -92,6 +92,7 @@ class ContentMessages { this.inprogress.push(upload); dis.dispatch({action: 'upload_started'}); + var error; var self = this; return def.promise.then(function() { upload.promise = matrixClient.uploadContent(file); @@ -103,11 +104,10 @@ class ContentMessages { dis.dispatch({action: 'upload_progress', upload: upload}); } }).then(function(url) { - dis.dispatch({action: 'upload_finished', upload: upload}); content.url = url; return matrixClient.sendMessage(roomId, content); }, function(err) { - dis.dispatch({action: 'upload_failed', upload: upload}); + error = err; if (!upload.canceled) { var desc = "The file '"+upload.fileName+"' failed to upload."; if (err.http_status == 413) { @@ -128,6 +128,12 @@ class ContentMessages { break; } } + if (error) { + dis.dispatch({action: 'upload_failed', upload: upload}); + } + else { + dis.dispatch({action: 'upload_finished', upload: upload}); + } }); } diff --git a/src/HtmlUtils.js b/src/HtmlUtils.js index 603f595951..0b7f17b2b2 100644 --- a/src/HtmlUtils.js +++ b/src/HtmlUtils.js @@ -17,13 +17,15 @@ limitations under the License. 'use strict'; var React = require('react'); +var ReactDOMServer = require('react-dom/server') var sanitizeHtml = require('sanitize-html'); var highlight = require('highlight.js'); var sanitizeHtmlParams = { allowedTags: [ - 'font', // custom to matrix. deliberately no h1/h2 to stop people shouting. + 'font', // custom to matrix for IRC-style font coloring 'del', // for markdown + // deliberately no h1/h2 to stop people shouting. 'h3', 'h4', 'h5', 'h6', 'blockquote', 'p', 'a', 'ul', 'ol', 'nl', 'li', 'b', 'i', 'strong', 'em', 'strike', 'code', 'hr', 'br', 'div', 'table', 'thead', 'caption', 'tbody', 'tr', 'th', 'td', 'pre' @@ -56,24 +58,17 @@ class Highlighter { this._key = 0; } - applyHighlights(safeSnippet, highlights) { + applyHighlights(safeSnippet, safeHighlights) { var lastOffset = 0; var offset; var nodes = []; - // XXX: when highlighting HTML, synapse performs the search on the plaintext body, - // but we're attempting to apply the highlights here to the HTML body. This is - // never going to end well - we really should be hooking into the sanitzer HTML - // parser to only attempt to highlight text nodes to avoid corrupting tags. - // If and when this happens, we'll probably have to split his method in two between - // HTML and plain-text highlighting. - - var safeHighlight = this.html ? sanitizeHtml(highlights[0], sanitizeHtmlParams) : highlights[0]; + var safeHighlight = safeHighlights[0]; while ((offset = safeSnippet.toLowerCase().indexOf(safeHighlight.toLowerCase(), lastOffset)) >= 0) { // handle preamble if (offset > lastOffset) { var subSnippet = safeSnippet.substring(lastOffset, offset); - nodes = nodes.concat(this._applySubHighlights(subSnippet, highlights)); + nodes = nodes.concat(this._applySubHighlights(subSnippet, safeHighlights)); } // do highlight @@ -85,15 +80,15 @@ class Highlighter { // handle postamble if (lastOffset != safeSnippet.length) { var subSnippet = safeSnippet.substring(lastOffset, undefined); - nodes = nodes.concat(this._applySubHighlights(subSnippet, highlights)); + nodes = nodes.concat(this._applySubHighlights(subSnippet, safeHighlights)); } return nodes; } - _applySubHighlights(safeSnippet, highlights) { - if (highlights[1]) { + _applySubHighlights(safeSnippet, safeHighlights) { + if (safeHighlights[1]) { // recurse into this range to check for the next set of highlight matches - return this.applyHighlights(safeSnippet, highlights.slice(1)); + return this.applyHighlights(safeSnippet, safeHighlights.slice(1)); } else { // no more highlights to be found, just return the unhighlighted string @@ -131,7 +126,7 @@ module.exports = { * * content: 'content' of the MatrixEvent * - * highlights: optional list of words to highlight + * highlights: optional list of words to highlight, ordered by longest word first * * opts.onHighlightClick: optional callback function to be called when a * highlighted word is clicked @@ -143,26 +138,42 @@ module.exports = { var safeBody; if (isHtml) { - safeBody = sanitizeHtml(content.formatted_body, sanitizeHtmlParams); + // XXX: We sanitize the HTML whilst also highlighting its text nodes, to avoid accidentally trying + // to highlight HTML tags themselves. However, this does mean that we don't highlight textnodes which + // are interrupted by HTML tags (not that we did before) - e.g. foobar won't get highlighted + // by an attempt to search for 'foobar'. Then again, the search query probably wouldn't work either + try { + if (highlights && highlights.length > 0) { + var highlighter = new Highlighter(isHtml, "mx_EventTile_searchHighlight", opts.onHighlightClick); + var safeHighlights = highlights.map(function(highlight) { + return sanitizeHtml(highlight, sanitizeHtmlParams); + }); + // XXX: hacky bodge to temporarily apply a textFilter to the sanitizeHtmlParams structure. + sanitizeHtmlParams.textFilter = function(safeText) { + return highlighter.applyHighlights(safeText, safeHighlights).map(function(span) { + // XXX: rather clunky conversion from the react nodes returned by applyHighlights + // (which need to be nodes for the non-html highlighting case), to convert them + // back into raw HTML given that's what sanitize-html works in terms of. + return ReactDOMServer.renderToString(span); + }).join(''); + }; + } + safeBody = sanitizeHtml(content.formatted_body, sanitizeHtmlParams); + } + finally { + delete sanitizeHtmlParams.textFilter; + } + return ; } else { safeBody = content.body; - } - - var body; - if (highlights && highlights.length > 0) { - var highlighter = new Highlighter(isHtml, "mx_EventTile_searchHighlight", opts.onHighlightClick); - body = highlighter.applyHighlights(safeBody, highlights); - } - else { - if (isHtml) { - body = ; + if (highlights && highlights.length > 0) { + var highlighter = new Highlighter(isHtml, "mx_EventTile_searchHighlight", opts.onHighlightClick); + return highlighter.applyHighlights(safeBody, highlights); } else { - body = safeBody; + return safeBody; } } - - return body; }, highlightDom: function(element) { diff --git a/src/Notifier.js b/src/Notifier.js index e52fd252fe..b64a001a5f 100644 --- a/src/Notifier.js +++ b/src/Notifier.js @@ -182,6 +182,9 @@ var Notifier = { if (state === "PREPARED" || state === "SYNCING") { this.isPrepared = true; } + else if (state === "STOPPED" || state === "ERROR") { + this.isPrepared = false; + } }, onRoomTimeline: function(ev, room, toStartOfTimeline) { diff --git a/src/SlashCommands.js b/src/SlashCommands.js index d4e7df3a16..fcdd33474e 100644 --- a/src/SlashCommands.js +++ b/src/SlashCommands.js @@ -352,11 +352,12 @@ module.exports = { }, getCommandList: function() { - // Return all the commands plus /me which isn't handled like normal commands + // Return all the commands plus /me and /markdown which aren't handled like normal commands var cmds = Object.keys(commands).sort().map(function(cmdKey) { return commands[cmdKey]; }) cmds.push(new Command("me", "", function(){})); + cmds.push(new Command("markdown", "", function(){})); return cmds; } diff --git a/src/Tinter.js b/src/Tinter.js index f08bdd7297..b258930425 100644 --- a/src/Tinter.js +++ b/src/Tinter.js @@ -76,6 +76,7 @@ var cached = false; function calcCssFixups() { for (var i = 0; i < document.styleSheets.length; i++) { var ss = document.styleSheets[i]; + if (!ss) continue; // well done safari >:( // Chromium apparently sometimes returns null here; unsure why. // see $14534907369972FRXBx:matrix.org in HQ // ...ah, it's because there's a third party extension like diff --git a/src/UserActivity.js b/src/UserActivity.js index 669b007934..384dc23059 100644 --- a/src/UserActivity.js +++ b/src/UserActivity.js @@ -30,6 +30,7 @@ class UserActivity { * Start listening to user activity */ start() { + document.onmousedown = this._onUserActivity.bind(this); document.onmousemove = this._onUserActivity.bind(this); document.onkeypress = this._onUserActivity.bind(this); // can't use document.scroll here because that's only the document @@ -46,6 +47,7 @@ class UserActivity { * Stop tracking user activity */ stop() { + document.onmousedown = undefined; document.onmousemove = undefined; document.onkeypress = undefined; window.removeEventListener('wheel', this._onUserActivity.bind(this), true); diff --git a/src/components/structures/MatrixChat.js b/src/components/structures/MatrixChat.js index e2f5c24b37..0a9231247c 100644 --- a/src/components/structures/MatrixChat.js +++ b/src/components/structures/MatrixChat.js @@ -175,7 +175,7 @@ module.exports = React.createClass({ guest: true }); }, function(err) { - console.error(err.data); + console.error("Failed to register as guest: " + err + " " + err.data); self._setAutoRegisterAsGuest(false); }); }, @@ -316,9 +316,6 @@ module.exports = React.createClass({ }); break; case 'view_room': - // by default we autoPeek rooms, unless we were called explicitly with - // autoPeek=false by something like RoomDirectory who has already peeked - this.setState({ autoPeek : payload.auto_peek === false ? false : true }); this._viewRoom(payload.room_id, payload.show_settings, payload.event_id); break; case 'view_prev_room': @@ -880,7 +877,6 @@ module.exports = React.createClass({ eventId={this.state.initialEventId} highlightedEventId={this.state.highlightedEventId} eventPixelOffset={this.state.initialEventPixelOffset} - autoPeek={this.state.autoPeek} key={this.state.currentRoom} ConferenceHandler={this.props.ConferenceHandler} /> ); @@ -974,7 +970,9 @@ module.exports = React.createClass({ onRegisterClick={this.onRegisterClick} homeserverUrl={this.props.config.default_hs_url} identityServerUrl={this.props.config.default_is_url} - onForgotPasswordClick={this.onForgotPasswordClick} /> + onForgotPasswordClick={this.onForgotPasswordClick} + onLoginAsGuestClick={this.props.enableGuest && this.props.config && this.props.config.default_hs_url ? this._registerAsGuest: undefined} + /> ); } } diff --git a/src/components/structures/RoomStatusBar.js b/src/components/structures/RoomStatusBar.js index 7a5f2ffdb2..a8f0de3e92 100644 --- a/src/components/structures/RoomStatusBar.js +++ b/src/components/structures/RoomStatusBar.js @@ -64,7 +64,10 @@ module.exports = React.createClass({ }, componentWillUnmount: function() { - MatrixClientPeg.get().removeListener("sync", this.onSyncStateChange); + // we may have entirely lost our client as we're logging out before clicking login on the guest bar... + if (MatrixClientPeg.get()) { + MatrixClientPeg.get().removeListener("sync", this.onSyncStateChange); + } }, onSyncStateChange: function(state, prevState) { diff --git a/src/components/structures/RoomView.js b/src/components/structures/RoomView.js index 820aaf407a..25c289ba96 100644 --- a/src/components/structures/RoomView.js +++ b/src/components/structures/RoomView.js @@ -60,12 +60,11 @@ module.exports = React.createClass({ displayName: 'RoomView', propTypes: { ConferenceHandler: React.PropTypes.any, - roomId: React.PropTypes.string, - autoPeek: React.PropTypes.bool, // Now unused, left here temporarily to avoid merge conflicts with @richvdh's branch. roomId: React.PropTypes.string.isRequired, - // id of an event to jump to. If not given, will use the read-up-to-marker. + // id of an event to jump to. If not given, will go to the end of the + // live timeline. eventId: React.PropTypes.string, // where to position the event given by eventId, in pixels from the @@ -76,14 +75,6 @@ module.exports = React.createClass({ // ID of an event to highlight. If undefined, no event will be highlighted. // Typically this will either be the same as 'eventId', or undefined. highlightedEventId: React.PropTypes.string, - - autoPeek: React.PropTypes.bool, // should we try to peek the room on mount, or has whoever invoked us already initiated a peek? - }, - - getDefaultProps: function() { - return { - autoPeek: true, - } }, /* properties in RoomView objects include: @@ -155,11 +146,6 @@ module.exports = React.createClass({ // We can /peek though. If it fails then we present the join UI. If it // succeeds then great, show the preview (but we still may be able to /join!). if (!this.state.room) { - if (!this.props.autoPeek) { - console.log("No room loaded, and autopeek disabled"); - return; - } - console.log("Attempting to peek into room %s", this.props.roomId); roomProm = MatrixClientPeg.get().peekInRoom(this.props.roomId).then((room) => { @@ -193,11 +179,6 @@ module.exports = React.createClass({ _initTimeline: function(props) { var initialEvent = props.eventId; - if (!initialEvent) { - // go to the 'read-up-to' mark if no explicit event given - initialEvent = this.state.readMarkerEventId; - } - var pixelOffset = props.eventPixelOffset; return this._loadTimeline(initialEvent, pixelOffset); }, @@ -486,20 +467,6 @@ module.exports = React.createClass({ readMarkerEventId: readMarkerEventId, readMarkerGhostEventId: readMarkerGhostEventId, }); - - - // if the scrollpanel is following the timeline, attempt to scroll - // it to bring the read message up to the middle of the panel. This - // will have no immediate effect (since we are already at the - // bottom), but will ensure that if there is no further user - // activity, but room activity continues, the read message will - // scroll up to the middle of the window, but no further. - // - // we do this here as well as in sendReadReceipt to deal with - // people using two clients at once. - if (this.refs.messagePanel && this.state.atEndOfLiveTimeline) { - this.refs.messagePanel.scrollToToken(readMarkerEventId); - } } }, @@ -585,14 +552,6 @@ module.exports = React.createClass({ window.addEventListener('resize', this.onResize); this.onResize(); - if (this.refs.roomView) { - var roomView = ReactDOM.findDOMNode(this.refs.roomView); - roomView.addEventListener('drop', this.onDrop); - roomView.addEventListener('dragover', this.onDragOver); - roomView.addEventListener('dragleave', this.onDragLeaveOrEnd); - roomView.addEventListener('dragend', this.onDragLeaveOrEnd); - } - this._updateTabCompleteList(); // XXX: EVIL HACK to autofocus inviting on empty rooms. @@ -630,6 +589,16 @@ module.exports = React.createClass({ // separate component to avoid this ridiculous dance. if (!this.refs.messagePanel) return; + if (this.refs.roomView) { + var roomView = ReactDOM.findDOMNode(this.refs.roomView); + if (!roomView.ondrop) { + roomView.addEventListener('drop', this.onDrop); + roomView.addEventListener('dragover', this.onDragOver); + roomView.addEventListener('dragleave', this.onDragLeaveOrEnd); + roomView.addEventListener('dragend', this.onDragLeaveOrEnd); + } + } + if (!this.refs.messagePanel.initialised) { this._initialiseMessagePanel(); } @@ -1159,19 +1128,6 @@ module.exports = React.createClass({ // it failed, so allow retries next time the user is active this.last_rr_sent_event_id = undefined; }); - - // if the scrollpanel is following the timeline, attempt to scroll - // it to bring the read message up to the middle of the panel. This - // will have no immediate effect (since we are already at the - // bottom), but will ensure that if there is no further user - // activity, but room activity continues, the read message will - // scroll up to the middle of the window, but no further. - // - // we do this here as well as in onRoomReceipt to cater for guest users - // (which do not send out read receipts). - if (this.state.atEndOfLiveTimeline) { - this.refs.messagePanel.scrollToToken(lastReadEvent.getId()); - } } }, @@ -1275,11 +1231,19 @@ module.exports = React.createClass({ self.setState({ rejecting: false }); - }, function(err) { - console.error("Failed to reject invite: %s", err); + }, function(error) { + console.error("Failed to reject invite: %s", error); + + var msg = error.message ? error.message : JSON.stringify(error); + var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); + Modal.createDialog(ErrorDialog, { + title: "Failed to reject invite", + description: msg + }); + self.setState({ rejecting: false, - rejectError: err + rejectError: error }); }); }, @@ -1473,14 +1437,14 @@ module.exports = React.createClass({ ); } else { - var joinErrorText = this.state.joinError ? "Failed to join room!" : ""; return (
-
{joinErrorText}
+ canJoin={ true } canPreview={ false } + spinner={this.state.joining} + />
@@ -1506,10 +1470,6 @@ module.exports = React.createClass({ } else { var inviteEvent = myMember.events.member; var inviterName = inviteEvent.sender ? inviteEvent.sender.name : inviteEvent.getSender(); - // XXX: Leaving this intentionally basic for now because invites are about to change totally - // FIXME: This comment is now outdated - what do we need to fix? ^ - var joinErrorText = this.state.joinError ? "Failed to join room!" : ""; - var rejectErrorText = this.state.rejectError ? "Failed to reject invite!" : ""; // We deliberately don't try to peek into invites, even if we have permission to peek // as they could be a spam vector. @@ -1522,9 +1482,9 @@ module.exports = React.createClass({ -
{joinErrorText}
-
{rejectErrorText}
+ canJoin={ true } canPreview={ false } + spinner={this.state.joining} + />
@@ -1588,13 +1548,17 @@ module.exports = React.createClass({ else if (this.state.guestsCanJoin && MatrixClientPeg.get().isGuest() && (!myMember || myMember.membership !== "join")) { aux = ( - + ); } else if (this.state.canPeek && (!myMember || myMember.membership !== "join")) { aux = ( - + ); } @@ -1714,15 +1678,22 @@ module.exports = React.createClass({ ); } else { - // it's important that stickyBottom = false on this, otherwise if somebody hits the - // bottom of the loaded events when viewing historical messages, we get stuck in a - // loop of paginating our way through the entire history of the room. + // give the messagepanel a stickybottom if we're at the end of the + // live timeline, so that the arrival of new events triggers a + // scroll. + // + // Make sure that stickyBottom is *false* if we can paginate + // forwards, otherwise if somebody hits the bottom of the loaded + // events when viewing historical messages, we get stuck in a loop + // of paginating our way through the entire history of the room. + var stickyBottom = !this._timelineWindow.canPaginate(EventTimeline.FORWARDS); + messagePanel = ( + stickyBottom={ stickyBottom }>
  • {this.getEventTiles()}
    diff --git a/src/components/structures/UserSettings.js b/src/components/structures/UserSettings.js index 8f7e138bc0..2b35c861b4 100644 --- a/src/components/structures/UserSettings.js +++ b/src/components/structures/UserSettings.js @@ -315,6 +315,16 @@ module.exports = React.createClass({ onFinished={this.onPasswordChanged} /> ); } + var notification_area; + if (!MatrixClientPeg.get().isGuest()) { + notification_area = (
    +

    Notifications

    + +
    + +
    +
    ); + } return (
    @@ -364,11 +374,7 @@ module.exports = React.createClass({ {accountJsx}
    -

    Notifications

    - -
    - -
    + {notification_area}

    Advanced

    diff --git a/src/components/structures/login/Login.js b/src/components/structures/login/Login.js index b853b8fd95..356439b0cc 100644 --- a/src/components/structures/login/Login.js +++ b/src/components/structures/login/Login.js @@ -35,7 +35,8 @@ module.exports = React.createClass({displayName: 'Login', // login shouldn't know or care how registration is done. onRegisterClick: React.PropTypes.func.isRequired, // login shouldn't care how password recovery is done. - onForgotPasswordClick: React.PropTypes.func + onForgotPasswordClick: React.PropTypes.func, + onLoginAsGuestClick: React.PropTypes.func, }, getDefaultProps: function() { @@ -167,6 +168,13 @@ module.exports = React.createClass({displayName: 'Login', var LoginFooter = sdk.getComponent("login.LoginFooter"); var loader = this.state.busy ?
    : null; + var loginAsGuestJsx; + if (this.props.onLoginAsGuestClick) { + loginAsGuestJsx = + + Login as guest + + } return (
    @@ -188,6 +196,7 @@ module.exports = React.createClass({displayName: 'Login', Create a new account + { loginAsGuestJsx }
    diff --git a/src/components/structures/login/Registration.js b/src/components/structures/login/Registration.js index 5666318368..9ec379a814 100644 --- a/src/components/structures/login/Registration.js +++ b/src/components/structures/login/Registration.js @@ -115,6 +115,9 @@ module.exports = React.createClass({ onProcessingRegistration: function(promise) { var self = this; promise.done(function(response) { + self.setState({ + busy: false + }); if (!response || !response.access_token) { console.warn( "FIXME: Register fulfilled without a final response, " + @@ -126,7 +129,7 @@ module.exports = React.createClass({ if (!response || !response.user_id || !response.access_token) { console.error("Final response is missing keys."); self.setState({ - errorText: "There was a problem processing the response." + errorText: "Registration failed on server" }); return; } @@ -136,9 +139,6 @@ module.exports = React.createClass({ identityServerUrl: self.registerLogic.getIdentityServerUrl(), accessToken: response.access_token }); - self.setState({ - busy: false - }); }, function(err) { if (err.message) { self.setState({ diff --git a/src/components/views/dialogs/LogoutPrompt.js b/src/components/views/dialogs/LogoutPrompt.js index 824924e999..67fedfe840 100644 --- a/src/components/views/dialogs/LogoutPrompt.js +++ b/src/components/views/dialogs/LogoutPrompt.js @@ -31,14 +31,22 @@ module.exports = React.createClass({ } }, + onKeyDown: function(e) { + if (e.keyCode === 27) { // escape + e.stopPropagation(); + e.preventDefault(); + this.cancelPrompt(); + } + }, + render: function() { return (
    Sign out?
    -
    - +
    +
    diff --git a/src/components/views/dialogs/SetDisplayNameDialog.js b/src/components/views/dialogs/SetDisplayNameDialog.js index d1287e2570..624bb50a46 100644 --- a/src/components/views/dialogs/SetDisplayNameDialog.js +++ b/src/components/views/dialogs/SetDisplayNameDialog.js @@ -26,9 +26,20 @@ module.exports = React.createClass({ }, getInitialState: function() { - return { - value: this.props.currentDisplayName || "Guest "+MatrixClientPeg.get().getUserIdLocalpart(), + if (this.props.currentDisplayName) { + return { value: this.props.currentDisplayName }; } + + if (MatrixClientPeg.get().isGuest()) { + return { value : "Guest " + MatrixClientPeg.get().getUserIdLocalpart() }; + } + else { + return { value : MatrixClientPeg.get().getUserIdLocalpart() }; + } + }, + + componentDidMount: function() { + this.refs.input_value.select(); }, getValue: function() { @@ -54,11 +65,12 @@ module.exports = React.createClass({ Set a Display Name
    - Your display name is how you'll appear to others when you speak in rooms. What would you like it to be? + Your display name is how you'll appear to others when you speak in rooms.
    + What would you like it to be?
    - diff --git a/src/components/views/elements/TruncatedList.js b/src/components/views/elements/TruncatedList.js index 275686c6be..3e174848d3 100644 --- a/src/components/views/elements/TruncatedList.js +++ b/src/components/views/elements/TruncatedList.js @@ -51,7 +51,7 @@ module.exports = React.createClass({ if (this.props.truncateAt >= 0) { var overflowCount = childCount - this.props.truncateAt; - if (overflowCount > 0) { + if (overflowCount > 1) { overflowJsx = this.props.createOverflowElement( overflowCount, childCount ); diff --git a/src/components/views/rooms/InviteMemberList.js b/src/components/views/rooms/InviteMemberList.js index 8bad76b7ba..480066771b 100644 --- a/src/components/views/rooms/InviteMemberList.js +++ b/src/components/views/rooms/InviteMemberList.js @@ -42,9 +42,11 @@ module.exports = React.createClass({ // TODO: Keep this list bleeding-edge up-to-date. Practically speaking, // it will do for now not being updated as random new users join different // rooms as this list will be reloaded every room swap. - this._userList = MatrixClientPeg.get().getUsers().filter((u) => { - return !this._room.hasMembershipState(u.userId, "join"); - }); + if (this._room) { + this._userList = MatrixClientPeg.get().getUsers().filter((u) => { + return !this._room.hasMembershipState(u.userId, "join"); + }); + } }, onInvite: function(ev) { @@ -87,7 +89,7 @@ module.exports = React.createClass({ } return ( - + +
    ); + } + if (this.props.inviterName) { joinBlock = (
    diff --git a/src/components/views/rooms/RoomSettings.js b/src/components/views/rooms/RoomSettings.js index 10af37ebf0..c811341610 100644 --- a/src/components/views/rooms/RoomSettings.js +++ b/src/components/views/rooms/RoomSettings.js @@ -37,10 +37,12 @@ module.exports = React.createClass({ }); var areNotifsMuted = false; - var roomPushRule = MatrixClientPeg.get().getRoomPushRule("global", this.props.room.roomId); - if (roomPushRule) { - if (0 <= roomPushRule.actions.indexOf("dont_notify")) { - areNotifsMuted = true; + if (!MatrixClientPeg.get().isGuest()) { + var roomPushRule = MatrixClientPeg.get().getRoomPushRule("global", this.props.room.roomId); + if (roomPushRule) { + if (0 <= roomPushRule.actions.indexOf("dont_notify")) { + areNotifsMuted = true; + } } } diff --git a/src/components/views/rooms/SearchableEntityList.js b/src/components/views/rooms/SearchableEntityList.js index c91e058e4c..fc0446ab41 100644 --- a/src/components/views/rooms/SearchableEntityList.js +++ b/src/components/views/rooms/SearchableEntityList.js @@ -140,34 +140,37 @@ var SearchableEntityList = React.createClass({ } var list; - if (this.props.truncateAt) { // caller wants list truncated - var TruncatedList = sdk.getComponent("elements.TruncatedList"); - list = ( - - {this.state.results.map((entity) => { - return entity.getJsx(); - })} - - ); - } - else { - list = ( -
    - {this.state.results.map((entity) => { - return entity.getJsx(); - })} -
    - ); + if (this.state.results.length) { + if (this.props.truncateAt) { // caller wants list truncated + var TruncatedList = sdk.getComponent("elements.TruncatedList"); + list = ( + + {this.state.results.map((entity) => { + return entity.getJsx(); + })} + + ); + } + else { + list = ( +
    + {this.state.results.map((entity) => { + return entity.getJsx(); + })} +
    + ); + } + list = + { list } + ; } return (
    - {inputBox} - - { list } - + { inputBox } + { list } { this.state.query.length ?

    : '' }
    ); diff --git a/src/components/views/settings/ChangeAvatar.js b/src/components/views/settings/ChangeAvatar.js index 89303856b2..9b03aba1a3 100644 --- a/src/components/views/settings/ChangeAvatar.js +++ b/src/components/views/settings/ChangeAvatar.js @@ -110,19 +110,17 @@ module.exports = React.createClass({ }, render: function() { - var RoomAvatar = sdk.getComponent('avatars.RoomAvatar'); var avatarImg; // Having just set an avatar we just display that since it will take a little // time to propagate through to the RoomAvatar. if (this.props.room && !this.avatarSet) { + var RoomAvatar = sdk.getComponent('avatars.RoomAvatar'); avatarImg = ; } else { - var style = { - width: this.props.width, - height: this.props.height, - objectFit: 'cover', - }; - avatarImg = ; + var BaseAvatar = sdk.getComponent("avatars.BaseAvatar"); + // XXX: FIXME: once we track in the JS what our own displayname is(!) then use it here rather than ? + avatarImg = } var uploadSection;