2015-06-23 18:41:25 +03:00
|
|
|
/*
|
2016-01-07 07:06:39 +03:00
|
|
|
Copyright 2015, 2016 OpenMarket Ltd
|
2015-06-23 18:41:25 +03:00
|
|
|
|
|
|
|
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.
|
|
|
|
*/
|
2016-03-24 14:25:41 +03:00
|
|
|
var React = require('react');
|
2015-12-15 02:37:34 +03:00
|
|
|
|
|
|
|
var CallHandler = require('../../../CallHandler');
|
2016-03-24 14:25:41 +03:00
|
|
|
var MatrixClientPeg = require('../../../MatrixClientPeg');
|
2016-04-02 23:52:05 +03:00
|
|
|
var Modal = require('../../../Modal');
|
2015-11-26 20:31:10 +03:00
|
|
|
var sdk = require('../../../index');
|
2016-03-24 14:25:41 +03:00
|
|
|
var dis = require('../../../dispatcher');
|
2016-06-01 14:24:21 +03:00
|
|
|
import Autocomplete from './Autocomplete';
|
2015-06-15 20:35:28 +03:00
|
|
|
|
2016-06-13 21:40:43 +03:00
|
|
|
import UserSettingsStore from '../../../UserSettingsStore';
|
|
|
|
|
2015-06-15 20:35:28 +03:00
|
|
|
|
2016-06-20 11:22:55 +03:00
|
|
|
export default class MessageComposer extends React.Component {
|
|
|
|
constructor(props, context) {
|
|
|
|
super(props, context);
|
|
|
|
this.onCallClick = this.onCallClick.bind(this);
|
|
|
|
this.onHangupClick = this.onHangupClick.bind(this);
|
|
|
|
this.onUploadClick = this.onUploadClick.bind(this);
|
|
|
|
this.onUploadFileSelected = this.onUploadFileSelected.bind(this);
|
|
|
|
this.onVoiceCallClick = this.onVoiceCallClick.bind(this);
|
|
|
|
this.onInputContentChanged = this.onInputContentChanged.bind(this);
|
2016-06-21 16:03:39 +03:00
|
|
|
this.onUpArrow = this.onUpArrow.bind(this);
|
|
|
|
this.onDownArrow = this.onDownArrow.bind(this);
|
2016-08-02 13:00:00 +03:00
|
|
|
this._tryComplete = this._tryComplete.bind(this);
|
|
|
|
this._onAutocompleteConfirm = this._onAutocompleteConfirm.bind(this);
|
2016-06-20 11:22:55 +03:00
|
|
|
|
|
|
|
this.state = {
|
2016-06-21 13:16:20 +03:00
|
|
|
autocompleteQuery: '',
|
2016-07-03 19:45:13 +03:00
|
|
|
selection: null,
|
2016-09-04 18:33:40 +03:00
|
|
|
selectionInfo: {
|
|
|
|
style: [],
|
|
|
|
blockType: null,
|
|
|
|
},
|
2016-06-01 14:24:21 +03:00
|
|
|
};
|
2016-07-03 19:45:13 +03:00
|
|
|
|
2016-06-20 11:22:55 +03:00
|
|
|
}
|
2016-06-01 14:24:21 +03:00
|
|
|
|
2016-06-20 11:22:55 +03:00
|
|
|
onUploadClick(ev) {
|
2016-06-01 01:59:36 +03:00
|
|
|
if (MatrixClientPeg.get().isGuest()) {
|
2016-07-03 19:45:13 +03:00
|
|
|
let NeedToRegisterDialog = sdk.getComponent("dialogs.NeedToRegisterDialog");
|
2016-06-01 01:59:36 +03:00
|
|
|
Modal.createDialog(NeedToRegisterDialog, {
|
|
|
|
title: "Please Register",
|
2016-07-03 19:45:13 +03:00
|
|
|
description: "Guest users can't upload files. Please register to upload.",
|
2016-06-01 01:59:36 +03:00
|
|
|
});
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2015-11-26 20:31:10 +03:00
|
|
|
this.refs.uploadInput.click();
|
2016-06-20 11:22:55 +03:00
|
|
|
}
|
2015-11-26 20:31:10 +03:00
|
|
|
|
2016-06-20 11:22:55 +03:00
|
|
|
onUploadFileSelected(ev) {
|
2016-07-03 19:45:13 +03:00
|
|
|
let files = ev.target.files;
|
2016-04-02 23:52:05 +03:00
|
|
|
|
2016-07-03 19:45:13 +03:00
|
|
|
let QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
|
|
|
|
let TintableSvg = sdk.getComponent("elements.TintableSvg");
|
2016-04-02 23:52:05 +03:00
|
|
|
|
2016-07-03 19:45:13 +03:00
|
|
|
let fileList = [];
|
|
|
|
for (let i=0; i<files.length; i++) {
|
2016-04-02 23:52:05 +03:00
|
|
|
fileList.push(<li>
|
2016-04-14 21:11:09 +03:00
|
|
|
<TintableSvg key={i} src="img/files.svg" width="16" height="16" /> {files[i].name}
|
2016-04-02 23:52:05 +03:00
|
|
|
</li>);
|
2015-11-26 20:31:10 +03:00
|
|
|
}
|
2016-04-02 23:52:05 +03:00
|
|
|
|
|
|
|
Modal.createDialog(QuestionDialog, {
|
|
|
|
title: "Upload Files",
|
|
|
|
description: (
|
|
|
|
<div>
|
|
|
|
<p>Are you sure you want upload the following files?</p>
|
|
|
|
<ul style={{listStyle: 'none', textAlign: 'left'}}>
|
|
|
|
{fileList}
|
|
|
|
</ul>
|
|
|
|
</div>
|
|
|
|
),
|
|
|
|
onFinished: (shouldUpload) => {
|
|
|
|
if(shouldUpload) {
|
|
|
|
// MessageComposer shouldn't have to rely on its parent passing in a callback to upload a file
|
|
|
|
if (files) {
|
|
|
|
for(var i=0; i<files.length; i++) {
|
|
|
|
this.props.uploadFile(files[i]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
this.refs.uploadInput.value = null;
|
2016-07-03 19:45:13 +03:00
|
|
|
},
|
2016-04-02 23:52:05 +03:00
|
|
|
});
|
2016-06-20 11:22:55 +03:00
|
|
|
}
|
2015-09-18 12:44:57 +03:00
|
|
|
|
2016-06-20 11:22:55 +03:00
|
|
|
onHangupClick() {
|
2015-12-15 02:37:34 +03:00
|
|
|
var call = CallHandler.getCallForRoom(this.props.room.roomId);
|
2015-12-17 05:49:09 +03:00
|
|
|
//var call = CallHandler.getAnyActiveCall();
|
2015-12-15 02:37:34 +03:00
|
|
|
if (!call) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
dis.dispatch({
|
|
|
|
action: 'hangup',
|
|
|
|
// hangup the call for this room, which may not be the room in props
|
|
|
|
// (e.g. conferences which will hangup the 1:1 room instead)
|
2016-07-03 19:45:13 +03:00
|
|
|
room_id: call.roomId,
|
2015-12-15 02:37:34 +03:00
|
|
|
});
|
2016-06-20 11:22:55 +03:00
|
|
|
}
|
2015-12-15 02:37:34 +03:00
|
|
|
|
2016-06-20 11:22:55 +03:00
|
|
|
onCallClick(ev) {
|
2015-11-26 20:31:10 +03:00
|
|
|
dis.dispatch({
|
|
|
|
action: 'place_call',
|
|
|
|
type: ev.shiftKey ? "screensharing" : "video",
|
2016-07-03 19:45:13 +03:00
|
|
|
room_id: this.props.room.roomId,
|
2015-11-26 20:31:10 +03:00
|
|
|
});
|
2016-06-20 11:22:55 +03:00
|
|
|
}
|
2015-11-26 20:31:10 +03:00
|
|
|
|
2016-06-20 11:22:55 +03:00
|
|
|
onVoiceCallClick(ev) {
|
2015-11-26 20:31:10 +03:00
|
|
|
dis.dispatch({
|
|
|
|
action: 'place_call',
|
|
|
|
type: 'voice',
|
2016-07-03 19:45:13 +03:00
|
|
|
room_id: this.props.room.roomId,
|
2015-11-26 20:31:10 +03:00
|
|
|
});
|
2016-06-20 11:22:55 +03:00
|
|
|
}
|
2015-11-26 20:31:10 +03:00
|
|
|
|
2016-09-04 18:33:40 +03:00
|
|
|
onInputContentChanged(content: string, selection: {start: number, end: number}, selectionInfo) {
|
2016-06-01 14:24:21 +03:00
|
|
|
this.setState({
|
2016-06-21 13:16:20 +03:00
|
|
|
autocompleteQuery: content,
|
2016-07-03 19:45:13 +03:00
|
|
|
selection,
|
2016-09-04 18:33:40 +03:00
|
|
|
selectionInfo,
|
2016-06-20 11:22:55 +03:00
|
|
|
});
|
|
|
|
}
|
2016-06-01 14:24:21 +03:00
|
|
|
|
2016-06-21 16:03:39 +03:00
|
|
|
onUpArrow() {
|
|
|
|
return this.refs.autocomplete.onUpArrow();
|
|
|
|
}
|
|
|
|
|
|
|
|
onDownArrow() {
|
|
|
|
return this.refs.autocomplete.onDownArrow();
|
|
|
|
}
|
|
|
|
|
2016-08-02 13:00:00 +03:00
|
|
|
_tryComplete(): boolean {
|
|
|
|
if (this.refs.autocomplete) {
|
|
|
|
return this.refs.autocomplete.onConfirm();
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
_onAutocompleteConfirm(range, completion) {
|
|
|
|
if (this.messageComposerInput) {
|
|
|
|
this.messageComposerInput.onConfirmAutocompletion(range, completion);
|
|
|
|
}
|
2016-06-21 16:03:39 +03:00
|
|
|
}
|
|
|
|
|
2016-09-04 18:33:40 +03:00
|
|
|
onFormatButtonClicked(name: "bold" | "italic" | "strike" | "quote" | "bullet" | "numbullet", event) {
|
|
|
|
this.messageComposerInput.onFormatButtonClicked(name, event);
|
|
|
|
}
|
|
|
|
|
2016-06-20 11:22:55 +03:00
|
|
|
render() {
|
2015-11-26 20:31:10 +03:00
|
|
|
var me = this.props.room.getMember(MatrixClientPeg.get().credentials.userId);
|
|
|
|
var uploadInputStyle = {display: 'none'};
|
|
|
|
var MemberAvatar = sdk.getComponent('avatars.MemberAvatar');
|
2016-01-06 05:11:07 +03:00
|
|
|
var TintableSvg = sdk.getComponent("elements.TintableSvg");
|
2016-06-13 21:40:43 +03:00
|
|
|
var MessageComposerInput = sdk.getComponent("rooms.MessageComposerInput" +
|
|
|
|
(UserSettingsStore.isFeatureEnabled('rich_text_editor') ? "" : "Old"));
|
2015-12-15 02:37:34 +03:00
|
|
|
|
2016-03-24 16:57:21 +03:00
|
|
|
var controls = [];
|
|
|
|
|
|
|
|
controls.push(
|
2016-04-13 04:02:55 +03:00
|
|
|
<div key="controls_avatar" className="mx_MessageComposer_avatar">
|
2016-03-24 16:57:21 +03:00
|
|
|
<MemberAvatar member={me} width={24} height={24} />
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
|
2015-12-15 02:37:34 +03:00
|
|
|
var callButton, videoCallButton, hangupButton;
|
|
|
|
if (this.props.callState && this.props.callState !== 'ended') {
|
|
|
|
hangupButton =
|
2016-04-13 04:02:55 +03:00
|
|
|
<div key="controls_hangup" className="mx_MessageComposer_hangup" onClick={this.onHangupClick}>
|
2015-12-15 02:37:34 +03:00
|
|
|
<img src="img/hangup.svg" alt="Hangup" title="Hangup" width="25" height="26"/>
|
|
|
|
</div>;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
callButton =
|
2016-04-13 04:02:55 +03:00
|
|
|
<div key="controls_call" className="mx_MessageComposer_voicecall" onClick={this.onVoiceCallClick} title="Voice call">
|
2016-08-01 18:45:27 +03:00
|
|
|
<TintableSvg src="img/icon-call.svg" width="35" height="35"/>
|
2016-07-03 19:45:13 +03:00
|
|
|
</div>;
|
2015-12-15 02:37:34 +03:00
|
|
|
videoCallButton =
|
2016-04-13 04:02:55 +03:00
|
|
|
<div key="controls_videocall" className="mx_MessageComposer_videocall" onClick={this.onCallClick} title="Video call">
|
2016-08-01 18:45:27 +03:00
|
|
|
<TintableSvg src="img/icons-video.svg" width="35" height="35"/>
|
2016-07-03 19:45:13 +03:00
|
|
|
</div>;
|
2015-12-15 02:37:34 +03:00
|
|
|
}
|
|
|
|
|
2016-03-24 16:57:21 +03:00
|
|
|
var canSendMessages = this.props.room.currentState.maySendMessage(
|
|
|
|
MatrixClientPeg.get().credentials.userId);
|
|
|
|
|
|
|
|
if (canSendMessages) {
|
|
|
|
// This also currently includes the call buttons. Really we should
|
|
|
|
// check separately for whether we can call, but this is slightly
|
|
|
|
// complex because of conference calls.
|
|
|
|
var uploadButton = (
|
2016-04-13 04:02:55 +03:00
|
|
|
<div key="controls_upload" className="mx_MessageComposer_upload"
|
2016-03-24 16:57:21 +03:00
|
|
|
onClick={this.onUploadClick} title="Upload file">
|
2016-07-13 19:46:56 +03:00
|
|
|
<TintableSvg src="img/icons-upload.svg" width="35" height="35"/>
|
2016-03-24 16:57:21 +03:00
|
|
|
<input ref="uploadInput" type="file"
|
|
|
|
style={uploadInputStyle}
|
2016-04-02 22:56:50 +03:00
|
|
|
multiple
|
2016-03-24 16:57:21 +03:00
|
|
|
onChange={this.onUploadFileSelected} />
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
|
2016-09-04 18:33:40 +03:00
|
|
|
const formattingButton = (
|
|
|
|
<img title="Text Formatting"
|
|
|
|
src="img/button-text-formatting.svg"
|
|
|
|
key="controls_formatting" />
|
|
|
|
);
|
|
|
|
|
2016-03-24 16:57:21 +03:00
|
|
|
controls.push(
|
2016-06-21 16:03:39 +03:00
|
|
|
<MessageComposerInput
|
2016-07-03 19:45:13 +03:00
|
|
|
ref={c => this.messageComposerInput = c}
|
2016-06-21 16:03:39 +03:00
|
|
|
key="controls_input"
|
|
|
|
onResize={this.props.onResize}
|
|
|
|
room={this.props.room}
|
2016-08-02 13:00:00 +03:00
|
|
|
tryComplete={this._tryComplete}
|
2016-06-21 16:03:39 +03:00
|
|
|
onUpArrow={this.onUpArrow}
|
|
|
|
onDownArrow={this.onDownArrow}
|
2016-07-04 23:02:40 +03:00
|
|
|
tabComplete={this.props.tabComplete} // used for old messagecomposerinput/tabcomplete
|
2016-06-21 16:03:39 +03:00
|
|
|
onContentChanged={this.onInputContentChanged} />,
|
2016-09-04 18:33:40 +03:00
|
|
|
formattingButton,
|
2016-03-24 16:57:21 +03:00
|
|
|
uploadButton,
|
|
|
|
hangupButton,
|
|
|
|
callButton,
|
2016-03-30 03:31:29 +03:00
|
|
|
videoCallButton
|
2016-03-24 16:57:21 +03:00
|
|
|
);
|
|
|
|
} else {
|
|
|
|
controls.push(
|
2016-04-13 04:02:55 +03:00
|
|
|
<div key="controls_error" className="mx_MessageComposer_noperm_error">
|
2016-03-24 16:57:21 +03:00
|
|
|
You do not have permission to post to this room
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2016-08-11 18:28:32 +03:00
|
|
|
let autoComplete;
|
|
|
|
if (UserSettingsStore.isFeatureEnabled('rich_text_editor')) {
|
|
|
|
autoComplete = <div className="mx_MessageComposer_autocomplete_wrapper">
|
|
|
|
<Autocomplete
|
|
|
|
ref="autocomplete"
|
|
|
|
onConfirm={this._onAutocompleteConfirm}
|
|
|
|
query={this.state.autocompleteQuery}
|
|
|
|
selection={this.state.selection} />
|
|
|
|
</div>;
|
|
|
|
}
|
|
|
|
|
2016-09-04 18:33:40 +03:00
|
|
|
|
|
|
|
const {style, blockType} = this.state.selectionInfo;
|
|
|
|
const formatButtons = ["bold", "italic", "strike", "quote", "bullet", "numbullet"].map(
|
|
|
|
name => {
|
|
|
|
const active = style.includes(name) || blockType === name;
|
|
|
|
const suffix = active ? '-o-n' : '';
|
|
|
|
const onFormatButtonClicked = this.onFormatButtonClicked.bind(this, name);
|
|
|
|
return <img className="mx_MessageComposer_format_button" title={name} onClick={onFormatButtonClicked} key={name} src={`img/button-text-${name}${suffix}.svg`} height="17" />;
|
|
|
|
},
|
|
|
|
);
|
|
|
|
|
2015-11-26 20:31:10 +03:00
|
|
|
return (
|
2016-04-12 19:18:32 +03:00
|
|
|
<div className="mx_MessageComposer mx_fadable" style={{ opacity: this.props.opacity }}>
|
2016-08-11 18:28:32 +03:00
|
|
|
{autoComplete}
|
2016-03-24 16:57:21 +03:00
|
|
|
<div className="mx_MessageComposer_wrapper">
|
|
|
|
<div className="mx_MessageComposer_row">
|
|
|
|
{controls}
|
2015-11-26 20:31:10 +03:00
|
|
|
</div>
|
|
|
|
</div>
|
2016-09-04 18:33:40 +03:00
|
|
|
{UserSettingsStore.isFeatureEnabled('rich_text_editor') ?
|
|
|
|
<div className="mx_MessageComposer_formatbar">
|
|
|
|
{formatButtons}
|
|
|
|
</div> : null
|
|
|
|
}
|
2015-11-26 20:31:10 +03:00
|
|
|
</div>
|
|
|
|
);
|
2015-09-18 12:44:57 +03:00
|
|
|
}
|
2016-06-20 11:22:55 +03:00
|
|
|
};
|
|
|
|
|
|
|
|
MessageComposer.propTypes = {
|
|
|
|
tabComplete: React.PropTypes.any,
|
|
|
|
|
|
|
|
// a callback which is called when the height of the composer is
|
|
|
|
// changed due to a change in content.
|
|
|
|
onResize: React.PropTypes.func,
|
|
|
|
|
|
|
|
// js-sdk Room object
|
|
|
|
room: React.PropTypes.object.isRequired,
|
|
|
|
|
|
|
|
// string representing the current voip call state
|
|
|
|
callState: React.PropTypes.string,
|
|
|
|
|
|
|
|
// callback when a file to upload is chosen
|
|
|
|
uploadFile: React.PropTypes.func.isRequired,
|
2015-06-15 20:35:28 +03:00
|
|
|
|
2016-06-20 11:22:55 +03:00
|
|
|
// opacity for dynamic UI fading effects
|
|
|
|
opacity: React.PropTypes.number
|
|
|
|
};
|