diff --git a/src/CallHandler.js b/src/CallHandler.js index bb46056d19..42cc681d08 100644 --- a/src/CallHandler.js +++ b/src/CallHandler.js @@ -310,9 +310,10 @@ function _onAction(payload) { placeCall(call); }, function(err) { const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); + console.error("Conference call failed: " + err); Modal.createDialog(ErrorDialog, { title: "Failed to set up conference call", - description: "Conference call failed: " + err, + description: "Conference call failed.", }); }); } diff --git a/src/ContentMessages.js b/src/ContentMessages.js index 17c8155c1b..4ab982c98f 100644 --- a/src/ContentMessages.js +++ b/src/ContentMessages.js @@ -276,7 +276,7 @@ class ContentMessages { sendContentToRoom(file, roomId, matrixClient) { const content = { - body: file.name, + body: file.name || 'Attachment', info: { size: file.size, } @@ -316,7 +316,7 @@ class ContentMessages { } const upload = { - fileName: file.name, + fileName: file.name || 'Attachment', roomId: roomId, total: 0, loaded: 0, diff --git a/src/TextForEvent.js b/src/TextForEvent.js index 3f772e9cfb..3e1659f392 100644 --- a/src/TextForEvent.js +++ b/src/TextForEvent.js @@ -116,7 +116,6 @@ function textForRoomNameEvent(ev) { function textForMessageEvent(ev) { var senderDisplayName = ev.sender && ev.sender.name ? ev.sender.name : ev.getSender(); - var message = senderDisplayName + ': ' + ev.getContent().body; if (ev.getContent().msgtype === "m.emote") { message = "* " + senderDisplayName + " " + message; diff --git a/src/UnknownDeviceErrorHandler.js b/src/UnknownDeviceErrorHandler.js index 88f4f57fe4..d842cc3a6e 100644 --- a/src/UnknownDeviceErrorHandler.js +++ b/src/UnknownDeviceErrorHandler.js @@ -1,3 +1,19 @@ +/* +Copyright 2017 Vector Creations 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. +*/ + import dis from './dispatcher'; import sdk from './index'; import Modal from './Modal'; diff --git a/src/components/structures/MatrixChat.js b/src/components/structures/MatrixChat.js index 44fdfcf23e..d4a460eb57 100644 --- a/src/components/structures/MatrixChat.js +++ b/src/components/structures/MatrixChat.js @@ -63,6 +63,13 @@ module.exports = React.createClass({ // called when the session load completes onLoadCompleted: React.PropTypes.func, + // Represents the screen to display as a result of parsing the initial + // window.location + initialScreenAfterLogin: React.PropTypes.shape({ + screen: React.PropTypes.string.isRequired, + params: React.PropTypes.object, + }), + // displayname, if any, to set on the device when logging // in/registering. defaultDeviceDisplayName: React.PropTypes.string, @@ -89,6 +96,12 @@ module.exports = React.createClass({ var s = { loading: true, screen: undefined, + screenAfterLogin: this.props.initialScreenAfterLogin, + + // Stashed guest credentials if the user logs out + // whilst logged in as a guest user (so they can change + // their mind & log back in) + guestCreds: null, // What the LoggedInView would be showing if visible page_type: null, @@ -184,11 +197,6 @@ module.exports = React.createClass({ componentWillMount: function() { SdkConfig.put(this.props.config); - // Stashed guest credentials if the user logs out - // whilst logged in as a guest user (so they can change - // their mind & log back in) - this.guestCreds = null; - // if the automatic session load failed, the error this.sessionLoadError = null; @@ -322,9 +330,6 @@ module.exports = React.createClass({ var self = this; switch (payload.action) { case 'logout': - if (MatrixClientPeg.get().isGuest()) { - this.guestCreds = MatrixClientPeg.getCredentials(); - } Lifecycle.logout(); break; case 'start_registration': @@ -344,7 +349,11 @@ module.exports = React.createClass({ this.notifyNewScreen('register'); break; case 'start_login': - if (this.state.logged_in) return; + if (MatrixClientPeg.get().isGuest()) { + this.setState({ + guestCreds: MatrixClientPeg.getCredentials(), + }); + } this.setStateForNewScreen({ screen: 'login', }); @@ -359,8 +368,8 @@ module.exports = React.createClass({ // also stash our credentials, then if we restore the session, // we can just do it the same way whether we started upgrade // registration or explicitly logged out - this.guestCreds = MatrixClientPeg.getCredentials(); this.setStateForNewScreen({ + guestCreds: MatrixClientPeg.getCredentials(), screen: "register", upgradeUsername: MatrixClientPeg.get().getUserIdLocalpart(), guestAccessToken: MatrixClientPeg.get().getAccessToken(), @@ -402,9 +411,10 @@ module.exports = React.createClass({ dis.dispatch({action: 'view_next_room'}); }, function(err) { modal.close(); + console.error("Failed to leave room " + payload.room_id + " " + err); Modal.createDialog(ErrorDialog, { title: "Failed to leave room", - description: err.toString() + description: "Server may be unavailable, overloaded, or you hit a bug." }); }); } @@ -708,18 +718,31 @@ module.exports = React.createClass({ * Called when a new logged in session has started */ _onLoggedIn: function(teamToken) { - this.guestCreds = null; - this.notifyNewScreen(''); this.setState({ - screen: undefined, + guestCreds: null, logged_in: true, }); + // If screenAfterLogin is set, use that, then null it so that a second login will + // result in view_home_page, _user_settings or _room_directory + if (this.state.screenAfterLogin && this.state.screenAfterLogin.screen) { + this.showScreen( + this.state.screenAfterLogin.screen, + this.state.screenAfterLogin.params + ); + this.setState({screenAfterLogin: null}); + return; + } else { + this.setState({screen: undefined}); + } + if (teamToken) { this._teamToken = teamToken; - this._setPage(PageTypes.HomePage); + dis.dispatch({action: 'view_home_page'}); } else if (this._is_registered) { - this._setPage(PageTypes.UserSettings); + dis.dispatch({action: 'view_user_settings'}); + } else { + dis.dispatch({action: 'view_room_directory'}); } }, @@ -768,12 +791,6 @@ module.exports = React.createClass({ cli.getRooms() )[0].roomId; self.setState({ready: true, currentRoomId: firstRoom, page_type: PageTypes.RoomView}); - } else { - if (self._teamToken) { - self.setState({ready: true, page_type: PageTypes.HomePage}); - } else { - self.setState({ready: true, page_type: PageTypes.RoomDirectory}); - } } } else { self.setState({ready: true, page_type: PageTypes.RoomView}); @@ -790,16 +807,7 @@ module.exports = React.createClass({ if (presentedId != undefined) { self.notifyNewScreen('room/'+presentedId); - } else { - // There is no information on presentedId - // so point user to fallback like /directory - if (self._teamToken) { - self.notifyNewScreen('home'); - } else { - self.notifyNewScreen('directory'); - } } - dis.dispatch({action: 'focus_composer'}); } else { self.setState({ready: true}); @@ -1002,9 +1010,9 @@ module.exports = React.createClass({ onReturnToGuestClick: function() { // reanimate our guest login - if (this.guestCreds) { - Lifecycle.setLoggedIn(this.guestCreds); - this.guestCreds = null; + if (this.state.guestCreds) { + Lifecycle.setLoggedIn(this.state.guestCreds); + this.setState({guestCreds: null}); } }, @@ -1153,7 +1161,7 @@ module.exports = React.createClass({ onLoggedIn={this.onRegistered} onLoginClick={this.onLoginClick} onRegisterClick={this.onRegisterClick} - onCancelClick={this.guestCreds ? this.onReturnToGuestClick : null} + onCancelClick={this.state.guestCreds ? this.onReturnToGuestClick : null} /> ); } else if (this.state.screen == 'forgot_password') { @@ -1180,7 +1188,7 @@ module.exports = React.createClass({ defaultDeviceDisplayName={this.props.defaultDeviceDisplayName} onForgotPasswordClick={this.onForgotPasswordClick} enableGuest={this.props.enableGuest} - onCancelClick={this.guestCreds ? this.onReturnToGuestClick : null} + onCancelClick={this.state.guestCreds ? this.onReturnToGuestClick : null} initialErrorText={this.sessionLoadError} /> ); diff --git a/src/components/structures/MessagePanel.js b/src/components/structures/MessagePanel.js index 0981b7b706..ff507b6f90 100644 --- a/src/components/structures/MessagePanel.js +++ b/src/components/structures/MessagePanel.js @@ -295,7 +295,10 @@ module.exports = React.createClass({ var last = (i == lastShownEventIndex); // Wrap consecutive member events in a ListSummary, ignore if redacted - if (isMembershipChange(mxEv) && EventTile.haveTileForEvent(mxEv)) { + if (isMembershipChange(mxEv) && + EventTile.haveTileForEvent(mxEv) && + !mxEv.isRedacted() + ) { let ts1 = mxEv.getTs(); // Ensure that the key of the MemberEventListSummary does not change with new // member events. This will prevent it from being re-created unnecessarily, and @@ -408,7 +411,9 @@ module.exports = React.createClass({ // is this a continuation of the previous message? var continuation = false; - if (prevEvent !== null && prevEvent.sender && mxEv.sender + + if (prevEvent !== null + && !prevEvent.isRedacted() && prevEvent.sender && mxEv.sender && mxEv.sender.userId === prevEvent.sender.userId && mxEv.getType() == prevEvent.getType()) { continuation = true; @@ -461,6 +466,7 @@ module.exports = React.createClass({ ref={this._collectEventNode.bind(this, eventId)} data-scroll-token={scrollToken}> 24h apart if (Math.abs(prevEvent.getTs() - nextEventDate.getTime()) > MILLIS_IN_DAY) { return true; } // Compare weekdays - return prevEvent.getDate().getDay() !== nextEventDate.getDay(); + return prevEventDate.getDay() !== nextEventDate.getDay(); }, // get a list of read receipts that should be shown next to this event diff --git a/src/components/structures/RoomView.js b/src/components/structures/RoomView.js index 47b572b6c3..345d0f6b80 100644 --- a/src/components/structures/RoomView.js +++ b/src/components/structures/RoomView.js @@ -936,9 +936,10 @@ module.exports = React.createClass({ return; } const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); + console.error("Failed to upload file " + file + " " + error); Modal.createDialog(ErrorDialog, { title: "Failed to upload file", - description: error.toString() + description: "Server may be unavailable, overloaded, or the file too big", }); }); }, @@ -1022,9 +1023,10 @@ module.exports = React.createClass({ }); }, function(error) { var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); + console.error("Search failed: " + error); Modal.createDialog(ErrorDialog, { title: "Search failed", - description: error.toString() + description: "Server may be unavailable, overloaded, or search timed out :(" }); }).finally(function() { self.setState({ diff --git a/src/components/structures/UserSettings.js b/src/components/structures/UserSettings.js index 10ffbca0d3..febdccd9c3 100644 --- a/src/components/structures/UserSettings.js +++ b/src/components/structures/UserSettings.js @@ -206,9 +206,10 @@ module.exports = React.createClass({ }); }, function(error) { var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); + console.error("Failed to load user settings: " + error); Modal.createDialog(ErrorDialog, { title: "Can't load user settings", - description: error.toString() + description: "Server may be unavailable or overloaded", }); }); }, @@ -246,10 +247,11 @@ module.exports = React.createClass({ self._refreshFromServer(); }, function(err) { var errMsg = (typeof err === "string") ? err : (err.error || ""); + console.error("Failed to set avatar: " + err); var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); Modal.createDialog(ErrorDialog, { title: "Error", - description: "Failed to set avatar. " + errMsg + description: "Failed to set avatar." }); }); }, @@ -286,6 +288,7 @@ module.exports = React.createClass({ errMsg += ` (HTTP status ${err.httpStatus})`; } var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); + console.error("Failed to change password: " + errMsg); Modal.createDialog(ErrorDialog, { title: "Error", description: errMsg @@ -337,9 +340,10 @@ module.exports = React.createClass({ }); }, (err) => { this.setState({email_add_pending: false}); + console.error("Unable to add email address " + email_address + " " + err); Modal.createDialog(ErrorDialog, { - title: "Unable to add email address", - description: err.message + title: "Error", + description: "Unable to add email address" }); }); ReactDOM.findDOMNode(this.refs.add_threepid_input).blur(); @@ -361,9 +365,10 @@ module.exports = React.createClass({ return this._refreshFromServer(); }).catch((err) => { const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); + console.error("Unable to remove contact information: " + err); Modal.createDialog(ErrorDialog, { - title: "Unable to remove contact information", - description: err.toString(), + title: "Error", + description: "Unable to remove contact information", }); }).done(); } @@ -401,9 +406,10 @@ module.exports = React.createClass({ }); } else { var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); + console.error("Unable to verify email address: " + err); Modal.createDialog(ErrorDialog, { - title: "Unable to verify email address", - description: err.toString(), + title: "Error", + description: "Unable to verify email address", }); } }); diff --git a/src/components/structures/login/Registration.js b/src/components/structures/login/Registration.js index cbc8929158..5f62407e65 100644 --- a/src/components/structures/login/Registration.js +++ b/src/components/structures/login/Registration.js @@ -185,7 +185,6 @@ module.exports = React.createClass({ const teamToken = data.team_token; // Store for use /w welcome pages window.localStorage.setItem('mx_team_token', teamToken); - this.props.onTeamMemberRegistered(teamToken); this._rtsClient.getTeam(teamToken).then((team) => { console.log( diff --git a/src/components/views/dialogs/BaseDialog.js b/src/components/views/dialogs/BaseDialog.js index e83403ef7c..0b2ca5225d 100644 --- a/src/components/views/dialogs/BaseDialog.js +++ b/src/components/views/dialogs/BaseDialog.js @@ -18,6 +18,7 @@ import React from 'react'; import * as KeyCode from '../../../KeyCode'; import AccessibleButton from '../elements/AccessibleButton'; +import sdk from '../../../index'; /** * Basic container for modal dialogs. @@ -65,15 +66,14 @@ export default React.createClass({ }, render: function() { + const TintableSvg = sdk.getComponent("elements.TintableSvg"); + return (
- Cancel +
{ this.props.title } diff --git a/src/components/views/dialogs/ChatCreateOrReuseDialog.js b/src/components/views/dialogs/ChatCreateOrReuseDialog.js index 7761e25010..1a6ddf0456 100644 --- a/src/components/views/dialogs/ChatCreateOrReuseDialog.js +++ b/src/components/views/dialogs/ChatCreateOrReuseDialog.js @@ -24,7 +24,7 @@ import Unread from '../../../Unread'; import classNames from 'classnames'; import createRoom from '../../../createRoom'; -export default class CreateOrReuseChatDialog extends React.Component { +export default class ChatCreateOrReuseDialog extends React.Component { constructor(props) { super(props); @@ -91,21 +91,25 @@ export default class CreateOrReuseChatDialog extends React.Component { const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog'); return ( - { this.props.onFinished(false) }} title='Create a new chat or reuse an existing one' > - You already have existing direct chats with this user: - {tiles} - {startNewChat} +
+ You already have existing direct chats with this user: +
+ {tiles} + {startNewChat} +
+
); } } -CreateOrReuseChatDialog.propTyps = { +ChatCreateOrReuseDialog.propTyps = { userId: React.PropTypes.string.isRequired, onFinished: React.PropTypes.func.isRequired, }; diff --git a/src/components/views/dialogs/ChatInviteDialog.js b/src/components/views/dialogs/ChatInviteDialog.js index 0e6a2b62e6..f958b8887c 100644 --- a/src/components/views/dialogs/ChatInviteDialog.js +++ b/src/components/views/dialogs/ChatInviteDialog.js @@ -318,8 +318,8 @@ module.exports = React.createClass({ console.error(err.stack); var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); Modal.createDialog(ErrorDialog, { - title: "Failure to invite", - description: err.toString() + title: "Error", + description: "Failed to invite", }); return null; }) @@ -331,8 +331,8 @@ module.exports = React.createClass({ console.error(err.stack); var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); Modal.createDialog(ErrorDialog, { - title: "Failure to invite user", - description: err.toString() + title: "Error", + description: "Failed to invite user", }); return null; }) @@ -352,8 +352,8 @@ module.exports = React.createClass({ console.error(err.stack); var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); Modal.createDialog(ErrorDialog, { - title: "Failure to invite", - description: err.toString() + title: "Error", + description: "Failed to invite", }); return null; }) diff --git a/src/components/views/dialogs/ConfirmUserActionDialog.js b/src/components/views/dialogs/ConfirmUserActionDialog.js index 4bd9cb669c..6cfaac65d4 100644 --- a/src/components/views/dialogs/ConfirmUserActionDialog.js +++ b/src/components/views/dialogs/ConfirmUserActionDialog.js @@ -97,7 +97,7 @@ export default React.createClass({ >
- +
{this.props.member.name}
{this.props.member.userId}
diff --git a/src/components/views/messages/UnknownBody.js b/src/components/views/messages/UnknownBody.js index 00784b18b0..a0fe8fdf74 100644 --- a/src/components/views/messages/UnknownBody.js +++ b/src/components/views/messages/UnknownBody.js @@ -22,10 +22,10 @@ module.exports = React.createClass({ displayName: 'UnknownBody', render: function() { - var content = this.props.mxEvent.getContent(); + const text = this.props.mxEvent.getContent().body; return ( - {content.body} + {text} ); }, diff --git a/src/components/views/rooms/EventTile.js b/src/components/views/rooms/EventTile.js index 5fb65096a5..48f0f282c1 100644 --- a/src/components/views/rooms/EventTile.js +++ b/src/components/views/rooms/EventTile.js @@ -25,18 +25,10 @@ var TextForEvent = require('../../../TextForEvent'); import WithMatrixClient from '../../../wrappers/WithMatrixClient'; var ContextualMenu = require('../../structures/ContextualMenu'); -var dispatcher = require("../../../dispatcher"); +import dis from '../../../dispatcher'; var ObjectUtils = require('../../../ObjectUtils'); -var bounce = false; -try { - if (global.localStorage) { - bounce = global.localStorage.getItem('avatar_bounce') == 'true'; - } -} catch (e) { -} - var eventTileTypes = { 'm.room.message': 'messages.MessageEvent', 'm.room.member' : 'messages.TextualEvent', @@ -73,6 +65,12 @@ module.exports = WithMatrixClient(React.createClass({ /* the MatrixEvent to show */ mxEvent: React.PropTypes.object.isRequired, + /* true if mxEvent is redacted. This is a prop because using mxEvent.isRedacted() + * might not be enough when deciding shouldComponentUpdate - prevProps.mxEvent + * references the same this.props.mxEvent. + */ + isRedacted: React.PropTypes.bool, + /* true if this is a continuation of the previous event (which has the * effect of not showing another avatar/displayname */ @@ -356,7 +354,7 @@ module.exports = WithMatrixClient(React.createClass({ onSenderProfileClick: function(event) { var mxEvent = this.props.mxEvent; - dispatcher.dispatch({ + dis.dispatch({ action: 'insert_displayname', displayname: (mxEvent.sender ? mxEvent.sender.name : mxEvent.getSender()).replace(' (IRC)', ''), }); @@ -372,6 +370,17 @@ module.exports = WithMatrixClient(React.createClass({ }); }, + onPermalinkClicked: function(e) { + // This allows the permalink to be opened in a new tab/window or copied as + // matrix.to, but also for it to enable routing within Riot when clicked. + e.preventDefault(); + dis.dispatch({ + action: 'view_room', + event_id: this.props.mxEvent.getId(), + room_id: this.props.mxEvent.getRoomId(), + }); + }, + render: function() { var MessageTimestamp = sdk.getComponent('messages.MessageTimestamp'); var SenderProfile = sdk.getComponent('messages.SenderProfile'); @@ -396,6 +405,7 @@ module.exports = WithMatrixClient(React.createClass({ var e2eEnabled = this.props.matrixClient.isRoomEncrypted(this.props.mxEvent.getRoomId()); var isSending = (['sending', 'queued', 'encrypting'].indexOf(this.props.eventSendStatus) !== -1); + const isRedacted = (eventType === 'm.room.message') && this.props.isRedacted; var classes = classNames({ mx_EventTile: true, @@ -412,8 +422,12 @@ module.exports = WithMatrixClient(React.createClass({ mx_EventTile_verified: this.state.verified == true, mx_EventTile_unverified: this.state.verified == false, mx_EventTile_bad: this.props.mxEvent.getContent().msgtype === 'm.bad.encrypted', + mx_EventTile_redacted: isRedacted, }); - var permalink = "https://matrix.to/#/" + this.props.mxEvent.getRoomId() +"/"+ this.props.mxEvent.getId(); + + const permalink = "https://matrix.to/#/" + + this.props.mxEvent.getRoomId() + "/" + + this.props.mxEvent.getId(); var readAvatars = this.getReadAvatars(); @@ -421,7 +435,10 @@ module.exports = WithMatrixClient(React.createClass({ let avatarSize; let needsSenderProfile; - if (this.props.tileShape === "notif") { + if (isRedacted) { + avatarSize = 0; + needsSenderProfile = false; + } else if (this.props.tileShape === "notif") { avatarSize = 24; needsSenderProfile = true; } else if (isInfoMessage) { @@ -486,6 +503,8 @@ module.exports = WithMatrixClient(React.createClass({ else if (e2eEnabled) { e2e = ; } + const timestamp = this.props.mxEvent.isRedacted() ? + null : ; if (this.props.tileShape === "notif") { var room = this.props.matrixClient.getRoom(this.props.mxEvent.getRoomId()); @@ -493,15 +512,15 @@ module.exports = WithMatrixClient(React.createClass({ return (
@@ -527,10 +546,14 @@ module.exports = WithMatrixClient(React.createClass({ tileShape={this.props.tileShape} onWidgetLoad={this.props.onWidgetLoad} />
- +
{ sender } - + { timestamp }
@@ -545,8 +568,8 @@ module.exports = WithMatrixClient(React.createClass({ { avatar } { sender }
- - + + { timestamp } { e2e } { @@ -278,9 +279,10 @@ module.exports = WithMatrixClient(React.createClass({ console.log("Ban success"); }, function(err) { const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); + console.error("Ban error: " + err); Modal.createDialog(ErrorDialog, { - title: "Ban error", - description: err.message, + title: "Error", + description: "Failed to ban user", }); } ).finally(()=>{ @@ -327,9 +329,10 @@ module.exports = WithMatrixClient(React.createClass({ // get out of sync if we force setState here! console.log("Mute toggle success"); }, function(err) { + console.error("Mute error: " + err); Modal.createDialog(ErrorDialog, { - title: "Mute error", - description: err.message + title: "Error", + description: "Failed to mute user", }); } ).finally(()=>{ @@ -375,9 +378,10 @@ module.exports = WithMatrixClient(React.createClass({ description: "This action cannot be performed by a guest user. Please register to be able to do this." }); } else { + console.error("Toggle moderator error:" + err); Modal.createDialog(ErrorDialog, { - title: "Moderator toggle error", - description: err.message + title: "Error", + description: "Failed to toggle moderator status", }); } } @@ -395,9 +399,10 @@ module.exports = WithMatrixClient(React.createClass({ console.log("Power change success"); }, function(err) { const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); + console.error("Failed to change power level " + err); Modal.createDialog(ErrorDialog, { - title: "Failure to change power level", - description: err.message + title: "Error", + description: "Failed to change power level", }); } ).finally(()=>{ diff --git a/src/components/views/rooms/MessageComposer.js b/src/components/views/rooms/MessageComposer.js index a29a3cf5ce..8a3b128908 100644 --- a/src/components/views/rooms/MessageComposer.js +++ b/src/components/views/rooms/MessageComposer.js @@ -91,8 +91,9 @@ export default class MessageComposer extends React.Component { this.refs.uploadInput.click(); } - onUploadFileSelected(ev) { - let files = ev.target.files; + onUploadFileSelected(files, isPasted) { + if (!isPasted) + files = files.target.files; let QuestionDialog = sdk.getComponent("dialogs.QuestionDialog"); let TintableSvg = sdk.getComponent("elements.TintableSvg"); @@ -100,7 +101,7 @@ export default class MessageComposer extends React.Component { let fileList = []; for (let i=0; i - {files[i].name} + {files[i].name || 'Attachment'} ); } @@ -171,7 +172,7 @@ export default class MessageComposer extends React.Component { } onUpArrow() { - return this.refs.autocomplete.onUpArrow(); + return this.refs.autocomplete.onUpArrow(); } onDownArrow() { @@ -299,6 +300,7 @@ export default class MessageComposer extends React.Component { tryComplete={this._tryComplete} onUpArrow={this.onUpArrow} onDownArrow={this.onDownArrow} + onUploadFileSelected={this.onUploadFileSelected} tabComplete={this.props.tabComplete} // used for old messagecomposerinput/tabcomplete onContentChanged={this.onInputContentChanged} onInputStateChanged={this.onInputStateChanged} />, diff --git a/src/components/views/rooms/MessageComposerInput.js b/src/components/views/rooms/MessageComposerInput.js index 61dd1e1b1c..d702b7558d 100644 --- a/src/components/views/rooms/MessageComposerInput.js +++ b/src/components/views/rooms/MessageComposerInput.js @@ -84,6 +84,7 @@ export default class MessageComposerInput extends React.Component { this.onAction = this.onAction.bind(this); this.handleReturn = this.handleReturn.bind(this); this.handleKeyCommand = this.handleKeyCommand.bind(this); + this.handlePastedFiles = this.handlePastedFiles.bind(this); this.onEditorContentChanged = this.onEditorContentChanged.bind(this); this.setEditorState = this.setEditorState.bind(this); this.onUpArrow = this.onUpArrow.bind(this); @@ -475,6 +476,10 @@ export default class MessageComposerInput extends React.Component { return false; } + handlePastedFiles(files) { + this.props.onUploadFileSelected(files, true); + } + handleReturn(ev) { if (ev.shiftKey) { this.onEditorContentChanged(RichUtils.insertSoftNewline(this.state.editorState)); @@ -504,7 +509,7 @@ export default class MessageComposerInput extends React.Component { var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); Modal.createDialog(ErrorDialog, { title: "Server error", - description: err.message + description: "Server unavailable, overloaded, or something else went wrong.", }); }); } @@ -728,6 +733,7 @@ export default class MessageComposerInput extends React.Component { keyBindingFn={MessageComposerInput.getKeyBinding} handleKeyCommand={this.handleKeyCommand} handleReturn={this.handleReturn} + handlePastedFiles={this.handlePastedFiles} stripPastedStyles={!this.state.isRichtextEnabled} onTab={this.onTab} onUpArrow={this.onUpArrow} @@ -757,6 +763,8 @@ MessageComposerInput.propTypes = { onDownArrow: React.PropTypes.func, + onUploadFileSelected: React.PropTypes.func, + // attempts to confirm currently selected completion, returns whether actually confirmed tryComplete: React.PropTypes.func, diff --git a/src/components/views/rooms/MessageComposerInputOld.js b/src/components/views/rooms/MessageComposerInputOld.js index 9f6464b69b..f0b650eb04 100644 --- a/src/components/views/rooms/MessageComposerInputOld.js +++ b/src/components/views/rooms/MessageComposerInputOld.js @@ -311,7 +311,7 @@ export default React.createClass({ var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); Modal.createDialog(ErrorDialog, { title: "Server error", - description: err.message + description: "Server unavailable, overloaded, or something else went wrong.", }); }); } diff --git a/src/components/views/rooms/RoomHeader.js b/src/components/views/rooms/RoomHeader.js index 1a8776cd96..94f2691f2c 100644 --- a/src/components/views/rooms/RoomHeader.js +++ b/src/components/views/rooms/RoomHeader.js @@ -115,9 +115,10 @@ module.exports = React.createClass({ changeAvatar.onFileSelected(ev).catch(function(err) { var errMsg = (typeof err === "string") ? err : (err.error || ""); var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); + console.error("Failed to set avatar: " + errMsg); Modal.createDialog(ErrorDialog, { title: "Error", - description: "Failed to set avatar. " + errMsg + description: "Failed to set avatar.", }); }).done(); }, diff --git a/src/components/views/rooms/RoomSettings.js b/src/components/views/rooms/RoomSettings.js index 3247f5a90b..2c7e1d7140 100644 --- a/src/components/views/rooms/RoomSettings.js +++ b/src/components/views/rooms/RoomSettings.js @@ -54,9 +54,10 @@ const BannedUser = React.createClass({ this.props.member.roomId, this.props.member.userId, ).catch((err) => { const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); + console.error("Failed to unban: " + err); Modal.createDialog(ErrorDialog, { - title: "Failed to unban", - description: err.message, + title: "Error", + description: "Failed to unban", }); }).done(); }, diff --git a/src/createRoom.js b/src/createRoom.js index 2a23fb0787..674fe23d28 100644 --- a/src/createRoom.js +++ b/src/createRoom.js @@ -102,9 +102,10 @@ function createRoom(opts) { }); return roomId; }, function(err) { + console.error("Failed to create room " + roomId + " " + err); Modal.createDialog(ErrorDialog, { title: "Failure to create room", - description: err.toString() + description: "Server may be unavailable, overloaded, or you hit a bug.", }); return null; });