Merge branch 'develop' into luke/UDE-file-upload

This commit is contained in:
Luke Barnard 2017-03-14 13:59:04 +00:00 committed by GitHub
commit 17c9fcbb85
22 changed files with 200 additions and 113 deletions

View file

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

View file

@ -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}>
<EventTile mxEvent={mxEv} continuation={continuation}
isRedacted={mxEv.isRedacted()}
onWidgetLoad={this._onWidgetLoad}
readReceipts={readReceipts}
readReceiptMap={this._readReceiptMap}
@ -481,13 +487,17 @@ module.exports = React.createClass({
// here.
return !this.props.suppressFirstDateSeparator;
}
const prevEventDate = prevEvent.getDate();
if (!nextEventDate || !prevEventDate) {
return false;
}
// Return early for events that are > 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

View file

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

View file

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

View file

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

View file

@ -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 (
<div onKeyDown={this._onKeyDown} className={this.props.className}>
<AccessibleButton onClick={this._onCancelClick}
className="mx_Dialog_cancelButton"
>
<img
src="img/cancel.svg" width="18" height="18"
alt="Cancel" title="Cancel"
/>
<TintableSvg src="img/icons-close-button.svg" width="35" height="35" />
</AccessibleButton>
<div className='mx_Dialog_title'>
{ this.props.title }

View file

@ -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 (
<BaseDialog className='mx_CreateOrReuseChatDialog'
<BaseDialog className='mx_ChatCreateOrReuseDialog'
onFinished={() => {
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}
<div className="mx_Dialog_content">
You already have existing direct chats with this user:
<div className="mx_ChatCreateOrReuseDialog_tiles">
{tiles}
{startNewChat}
</div>
</div>
</BaseDialog>
);
}
}
CreateOrReuseChatDialog.propTyps = {
ChatCreateOrReuseDialog.propTyps = {
userId: React.PropTypes.string.isRequired,
onFinished: React.PropTypes.func.isRequired,
};

View file

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

View file

@ -97,7 +97,7 @@ export default React.createClass({
>
<div className="mx_Dialog_content">
<div className="mx_ConfirmUserActionDialog_avatar">
<MemberAvatar member={this.props.member} width={72} height={72} />
<MemberAvatar member={this.props.member} width={48} height={48} />
</div>
<div className="mx_ConfirmUserActionDialog_name">{this.props.member.name}</div>
<div className="mx_ConfirmUserActionDialog_userId">{this.props.member.userId}</div>

View file

@ -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 (
<span className="mx_UnknownBody">
{content.body}
{text}
</span>
);
},

View file

@ -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 = <img onClick={ this.onCryptoClicked } className="mx_EventTile_e2eIcon" src="img/e2e-unencrypted.svg" width="12" height="12"/>;
}
const timestamp = this.props.mxEvent.isRedacted() ?
null : <MessageTimestamp ts={this.props.mxEvent.getTs()} />;
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 (
<div className={classes}>
<div className="mx_EventTile_roomName">
<a href={ permalink }>
<a href={ permalink } onClick={this.onPermalinkClicked}>
{ room ? room.name : '' }
</a>
</div>
<div className="mx_EventTile_senderDetails">
{ avatar }
<a href={ permalink }>
<a href={ permalink } onClick={this.onPermalinkClicked}>
{ sender }
<MessageTimestamp ts={this.props.mxEvent.getTs()} />
{ timestamp }
</a>
</div>
<div className="mx_EventTile_line" >
@ -527,10 +546,14 @@ module.exports = WithMatrixClient(React.createClass({
tileShape={this.props.tileShape}
onWidgetLoad={this.props.onWidgetLoad} />
</div>
<a className="mx_EventTile_senderDetailsLink" href={ permalink }>
<a
className="mx_EventTile_senderDetailsLink"
href={ permalink }
onClick={this.onPermalinkClicked}
>
<div className="mx_EventTile_senderDetails">
{ sender }
<MessageTimestamp ts={this.props.mxEvent.getTs()} />
{ timestamp }
</div>
</a>
</div>
@ -545,8 +568,8 @@ module.exports = WithMatrixClient(React.createClass({
{ avatar }
{ sender }
<div className="mx_EventTile_line">
<a href={ permalink }>
<MessageTimestamp ts={this.props.mxEvent.getTs()} />
<a href={ permalink } onClick={this.onPermalinkClicked}>
{ timestamp }
</a>
{ e2e }
<EventTileType ref="tile"
@ -564,7 +587,8 @@ module.exports = WithMatrixClient(React.createClass({
}));
module.exports.haveTileForEvent = function(e) {
if (e.isRedacted()) return false;
// Only messages have a tile (black-rectangle) if redacted
if (e.isRedacted() && e.getType() !== 'm.room.message') return false;
if (eventTileTypes[e.getType()] == undefined) return false;
if (eventTileTypes[e.getType()] == 'messages.TextualEvent') {
return TextForEvent.textForEvent(e) !== '';

View file

@ -237,9 +237,10 @@ module.exports = WithMatrixClient(React.createClass({
console.log("Kick success");
}, function(err) {
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
console.error("Kick error: " + err);
Modal.createDialog(ErrorDialog, {
title: "Kick error",
description: err.message
title: "Error",
description: "Failed to kick user",
});
}
).finally(()=>{
@ -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(()=>{

View file

@ -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.length; i++) {
fileList.push(<li key={i}>
<TintableSvg key={i} src="img/files.svg" width="16" height="16" /> {files[i].name}
<TintableSvg key={i} src="img/files.svg" width="16" height="16" /> {files[i].name || 'Attachment'}
</li>);
}
@ -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} />,

View file

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

View file

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

View file

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

View file

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