2015-06-23 18:41:25 +03:00
|
|
|
/*
|
2016-01-07 07:06:39 +03:00
|
|
|
Copyright 2015, 2016 OpenMarket Ltd
|
2018-08-21 17:56:56 +03:00
|
|
|
Copyright 2017, 2018 New Vector 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.
|
|
|
|
*/
|
2017-06-12 16:50:25 +03:00
|
|
|
import React from 'react';
|
2017-12-26 04:03:18 +03:00
|
|
|
import PropTypes from 'prop-types';
|
2018-07-11 13:39:55 +03:00
|
|
|
import { _t, _td } from '../../../languageHandler';
|
2017-06-12 16:50:25 +03:00
|
|
|
import CallHandler from '../../../CallHandler';
|
|
|
|
import MatrixClientPeg from '../../../MatrixClientPeg';
|
|
|
|
import Modal from '../../../Modal';
|
|
|
|
import sdk from '../../../index';
|
|
|
|
import dis from '../../../dispatcher';
|
2019-01-17 12:29:37 +03:00
|
|
|
import RoomViewStore from '../../../stores/RoomViewStore';
|
2017-11-04 08:19:45 +03:00
|
|
|
import SettingsStore, {SettingLevel} from "../../../settings/SettingsStore";
|
2018-02-26 01:10:38 +03:00
|
|
|
import Stickerpicker from './Stickerpicker';
|
2018-08-21 17:56:56 +03:00
|
|
|
import { makeRoomPermalink } from '../../../matrix-to';
|
2019-02-01 15:40:42 +03:00
|
|
|
import classNames from 'classnames';
|
|
|
|
|
|
|
|
import E2EIcon from './E2EIcon';
|
2015-06-15 20:35:28 +03:00
|
|
|
|
2018-07-11 13:39:55 +03:00
|
|
|
const formatButtonList = [
|
|
|
|
_td("bold"),
|
|
|
|
_td("italic"),
|
|
|
|
_td("deleted"),
|
|
|
|
_td("underlined"),
|
|
|
|
_td("inline-code"),
|
|
|
|
_td("block-quote"),
|
|
|
|
_td("bulleted-list"),
|
|
|
|
_td("numbered-list"),
|
|
|
|
];
|
|
|
|
|
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);
|
2017-05-17 03:41:42 +03:00
|
|
|
this.uploadFiles = this.uploadFiles.bind(this);
|
2016-06-20 11:22:55 +03:00
|
|
|
this.onVoiceCallClick = this.onVoiceCallClick.bind(this);
|
2016-08-02 13:00:00 +03:00
|
|
|
this._onAutocompleteConfirm = this._onAutocompleteConfirm.bind(this);
|
2016-09-05 15:08:53 +03:00
|
|
|
this.onToggleFormattingClicked = this.onToggleFormattingClicked.bind(this);
|
|
|
|
this.onToggleMarkdownClicked = this.onToggleMarkdownClicked.bind(this);
|
|
|
|
this.onInputStateChanged = this.onInputStateChanged.bind(this);
|
2016-09-15 21:25:53 +03:00
|
|
|
this.onEvent = this.onEvent.bind(this);
|
2018-08-21 17:56:56 +03:00
|
|
|
this._onRoomStateEvents = this._onRoomStateEvents.bind(this);
|
2017-12-18 22:49:38 +03:00
|
|
|
this._onRoomViewStoreUpdate = this._onRoomViewStoreUpdate.bind(this);
|
2018-08-21 17:56:56 +03:00
|
|
|
this._onTombstoneClick = this._onTombstoneClick.bind(this);
|
2018-01-16 21:14:32 +03:00
|
|
|
|
2016-06-20 11:22:55 +03:00
|
|
|
this.state = {
|
2016-09-05 15:08:53 +03:00
|
|
|
inputState: {
|
2018-05-14 01:34:00 +03:00
|
|
|
marks: [],
|
2016-09-04 18:33:40 +03:00
|
|
|
blockType: null,
|
2018-05-20 18:30:39 +03:00
|
|
|
isRichTextEnabled: SettingsStore.getValue('MessageComposerInput.isRichTextEnabled'),
|
2016-09-04 18:33:40 +03:00
|
|
|
},
|
2017-10-29 10:43:52 +03:00
|
|
|
showFormatting: SettingsStore.getValue('MessageComposer.showFormatting'),
|
2019-01-17 12:29:37 +03:00
|
|
|
isQuoting: Boolean(RoomViewStore.getQuotingEvent()),
|
2018-08-21 17:56:56 +03:00
|
|
|
tombstone: this._getRoomTombstone(),
|
2016-06-01 14:24:21 +03:00
|
|
|
};
|
2016-06-20 11:22:55 +03:00
|
|
|
}
|
2016-06-01 14:24:21 +03:00
|
|
|
|
2016-09-15 21:25:53 +03:00
|
|
|
componentDidMount() {
|
|
|
|
// N.B. using 'event' rather than 'RoomEvents' otherwise the crypto handler
|
|
|
|
// for 'event' fires *after* 'RoomEvent', and our room won't have yet been
|
|
|
|
// marked as encrypted.
|
|
|
|
// XXX: fragile as all hell - fixme somehow, perhaps with a dedicated Room.encryption event or something.
|
|
|
|
MatrixClientPeg.get().on("event", this.onEvent);
|
2018-08-21 17:56:56 +03:00
|
|
|
MatrixClientPeg.get().on("RoomState.events", this._onRoomStateEvents);
|
2019-01-17 12:29:37 +03:00
|
|
|
this._roomStoreToken = RoomViewStore.addListener(this._onRoomViewStoreUpdate);
|
2018-08-23 01:02:20 +03:00
|
|
|
this._waitForOwnMember();
|
|
|
|
}
|
|
|
|
|
|
|
|
_waitForOwnMember() {
|
|
|
|
// if we have the member already, do that
|
|
|
|
const me = this.props.room.getMember(MatrixClientPeg.get().getUserId());
|
|
|
|
if (me) {
|
|
|
|
this.setState({me});
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
// Otherwise, wait for member loading to finish and then update the member for the avatar.
|
|
|
|
// The members should already be loading, and loadMembersIfNeeded
|
|
|
|
// will return the promise for the existing operation
|
|
|
|
this.props.room.loadMembersIfNeeded().then(() => {
|
|
|
|
const me = this.props.room.getMember(MatrixClientPeg.get().getUserId());
|
|
|
|
this.setState({me});
|
|
|
|
});
|
2016-09-15 21:25:53 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
componentWillUnmount() {
|
2016-09-29 18:57:10 +03:00
|
|
|
if (MatrixClientPeg.get()) {
|
|
|
|
MatrixClientPeg.get().removeListener("event", this.onEvent);
|
2018-08-21 17:56:56 +03:00
|
|
|
MatrixClientPeg.get().removeListener("RoomState.events", this._onRoomStateEvents);
|
2016-09-29 18:57:10 +03:00
|
|
|
}
|
2017-12-18 22:49:38 +03:00
|
|
|
if (this._roomStoreToken) {
|
|
|
|
this._roomStoreToken.remove();
|
|
|
|
}
|
2016-09-15 21:25:53 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
onEvent(event) {
|
|
|
|
if (event.getType() !== 'm.room.encryption') return;
|
|
|
|
if (event.getRoomId() !== this.props.room.roomId) return;
|
|
|
|
this.forceUpdate();
|
|
|
|
}
|
|
|
|
|
2018-08-21 17:56:56 +03:00
|
|
|
_onRoomStateEvents(ev, state) {
|
|
|
|
if (ev.getRoomId() !== this.props.room.roomId) return;
|
|
|
|
|
|
|
|
if (ev.getType() === 'm.room.tombstone') {
|
|
|
|
this.setState({tombstone: this._getRoomTombstone()});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
_getRoomTombstone() {
|
|
|
|
return this.props.room.currentState.getStateEvents('m.room.tombstone', '');
|
|
|
|
}
|
|
|
|
|
2017-12-18 22:49:38 +03:00
|
|
|
_onRoomViewStoreUpdate() {
|
2019-01-17 12:29:37 +03:00
|
|
|
const isQuoting = Boolean(RoomViewStore.getQuotingEvent());
|
2017-12-18 22:49:38 +03:00
|
|
|
if (this.state.isQuoting === isQuoting) return;
|
|
|
|
this.setState({ isQuoting });
|
|
|
|
}
|
|
|
|
|
2016-06-20 11:22:55 +03:00
|
|
|
onUploadClick(ev) {
|
2016-06-01 01:59:36 +03:00
|
|
|
if (MatrixClientPeg.get().isGuest()) {
|
2019-01-17 12:29:37 +03:00
|
|
|
dis.dispatch({action: 'require_registration'});
|
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
|
|
|
|
2017-05-17 03:41:42 +03:00
|
|
|
onUploadFileSelected(files) {
|
2018-05-03 18:28:46 +03:00
|
|
|
const tfiles = files.target.files;
|
2018-05-03 19:02:37 +03:00
|
|
|
this.uploadFiles(tfiles);
|
2017-05-17 03:41:42 +03:00
|
|
|
}
|
2016-04-02 23:52:05 +03:00
|
|
|
|
2017-05-17 03:41:42 +03:00
|
|
|
uploadFiles(files) {
|
2017-10-11 19:56:17 +03:00
|
|
|
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
|
|
|
|
const TintableSvg = sdk.getComponent("elements.TintableSvg");
|
2016-04-02 23:52:05 +03:00
|
|
|
|
2017-10-11 19:56:17 +03:00
|
|
|
const fileList = [];
|
2018-05-03 18:28:46 +03:00
|
|
|
const acceptedFiles = [];
|
|
|
|
const failedFiles = [];
|
|
|
|
|
2016-07-03 19:45:13 +03:00
|
|
|
for (let i=0; i<files.length; i++) {
|
2018-06-23 16:28:20 +03:00
|
|
|
const fileAcceptedOrError = this.props.uploadAllowed(files[i]);
|
2018-05-03 18:28:46 +03:00
|
|
|
if (fileAcceptedOrError === true) {
|
|
|
|
acceptedFiles.push(<li key={i}>
|
2019-01-11 04:37:28 +03:00
|
|
|
<TintableSvg key={i} src={require("../../../../res/img/files.svg")} width="16" height="16" /> { files[i].name || _t('Attachment') }
|
2018-05-03 18:28:46 +03:00
|
|
|
</li>);
|
|
|
|
fileList.push(files[i]);
|
|
|
|
} else {
|
|
|
|
failedFiles.push(<li key={i}>
|
2019-01-11 04:37:28 +03:00
|
|
|
<TintableSvg key={i} src={require("../../../../res/img/files.svg")} width="16" height="16" /> { files[i].name || _t('Attachment') } <p>{ _t('Reason') + ": " + fileAcceptedOrError}</p>
|
2018-05-03 18:28:46 +03:00
|
|
|
</li>);
|
|
|
|
}
|
2015-11-26 20:31:10 +03:00
|
|
|
}
|
2016-04-02 23:52:05 +03:00
|
|
|
|
2019-01-17 12:29:37 +03:00
|
|
|
const isQuoting = Boolean(RoomViewStore.getQuotingEvent());
|
2018-04-04 13:19:13 +03:00
|
|
|
let replyToWarning = null;
|
|
|
|
if (isQuoting) {
|
|
|
|
replyToWarning = <p>{
|
|
|
|
_t('At this time it is not possible to reply with a file so this will be sent without being a reply.')
|
|
|
|
}</p>;
|
|
|
|
}
|
|
|
|
|
2018-05-03 18:28:46 +03:00
|
|
|
const acceptedFilesPart = acceptedFiles.length === 0 ? null : (
|
|
|
|
<div>
|
|
|
|
<p>{ _t('Are you sure you want to upload the following files?') }</p>
|
|
|
|
<ul style={{listStyle: 'none', textAlign: 'left'}}>
|
|
|
|
{ acceptedFiles }
|
|
|
|
</ul>
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
|
|
|
|
const failedFilesPart = failedFiles.length === 0 ? null : (
|
|
|
|
<div>
|
|
|
|
<p>{ _t('The following files cannot be uploaded:') }</p>
|
|
|
|
<ul style={{listStyle: 'none', textAlign: 'left'}}>
|
|
|
|
{ failedFiles }
|
|
|
|
</ul>
|
|
|
|
</div>
|
|
|
|
);
|
2018-06-23 16:28:20 +03:00
|
|
|
let buttonText;
|
|
|
|
if (acceptedFiles.length > 0 && failedFiles.length > 0) {
|
|
|
|
buttonText = "Upload selected"
|
|
|
|
} else if (failedFiles.length > 0) {
|
|
|
|
buttonText = "Close"
|
|
|
|
}
|
2018-05-03 18:28:46 +03:00
|
|
|
|
2017-07-27 19:19:18 +03:00
|
|
|
Modal.createTrackedDialog('Upload Files confirmation', '', QuestionDialog, {
|
2017-05-23 17:16:31 +03:00
|
|
|
title: _t('Upload Files'),
|
2016-04-02 23:52:05 +03:00
|
|
|
description: (
|
|
|
|
<div>
|
2018-05-03 18:28:46 +03:00
|
|
|
{ acceptedFilesPart }
|
|
|
|
{ failedFilesPart }
|
2018-04-04 13:19:13 +03:00
|
|
|
{ replyToWarning }
|
2016-04-02 23:52:05 +03:00
|
|
|
</div>
|
|
|
|
),
|
2018-06-23 16:28:20 +03:00
|
|
|
hasCancelButton: acceptedFiles.length > 0,
|
|
|
|
button: buttonText,
|
2016-04-02 23:52:05 +03:00
|
|
|
onFinished: (shouldUpload) => {
|
2017-11-16 16:19:36 +03:00
|
|
|
if (shouldUpload) {
|
2016-04-02 23:52:05 +03:00
|
|
|
// MessageComposer shouldn't have to rely on its parent passing in a callback to upload a file
|
2018-05-03 18:28:46 +03:00
|
|
|
if (fileList) {
|
|
|
|
for (let i=0; i<fileList.length; i++) {
|
|
|
|
this.props.uploadFile(fileList[i]);
|
2016-04-02 23:52:05 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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() {
|
2017-05-17 13:31:01 +03:00
|
|
|
const 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;
|
|
|
|
}
|
2019-01-17 12:29:37 +03:00
|
|
|
dis.dispatch({
|
2015-12-15 02:37:34 +03:00
|
|
|
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
|
|
|
|
2017-06-13 16:35:13 +03:00
|
|
|
onCallClick(ev) {
|
2019-01-17 12:29:37 +03:00
|
|
|
dis.dispatch({
|
2017-06-27 13:38:14 +03:00
|
|
|
action: 'place_call',
|
|
|
|
type: ev.shiftKey ? "screensharing" : "video",
|
|
|
|
room_id: this.props.room.roomId,
|
|
|
|
});
|
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) {
|
2019-01-17 12:29:37 +03:00
|
|
|
dis.dispatch({
|
2017-06-27 13:38:14 +03:00
|
|
|
action: 'place_call',
|
|
|
|
type: "voice",
|
|
|
|
room_id: this.props.room.roomId,
|
|
|
|
});
|
2016-06-20 11:22:55 +03:00
|
|
|
}
|
2015-11-26 20:31:10 +03:00
|
|
|
|
2016-09-05 15:08:53 +03:00
|
|
|
onInputStateChanged(inputState) {
|
|
|
|
this.setState({inputState});
|
|
|
|
}
|
|
|
|
|
2016-08-02 13:00:00 +03:00
|
|
|
_onAutocompleteConfirm(range, completion) {
|
|
|
|
if (this.messageComposerInput) {
|
2016-09-13 13:11:52 +03:00
|
|
|
this.messageComposerInput.setDisplayedCompletion(range, completion);
|
2016-08-02 13:00:00 +03:00
|
|
|
}
|
2016-06-21 16:03:39 +03:00
|
|
|
}
|
|
|
|
|
2018-05-21 00:39:40 +03:00
|
|
|
onFormatButtonClicked(name, event) {
|
2016-09-05 15:08:53 +03:00
|
|
|
event.preventDefault();
|
2016-09-04 18:33:40 +03:00
|
|
|
this.messageComposerInput.onFormatButtonClicked(name, event);
|
|
|
|
}
|
|
|
|
|
2016-09-05 15:08:53 +03:00
|
|
|
onToggleFormattingClicked() {
|
2017-11-04 08:19:45 +03:00
|
|
|
SettingsStore.setValue("MessageComposer.showFormatting", null, SettingLevel.DEVICE, !this.state.showFormatting);
|
2016-09-05 15:08:53 +03:00
|
|
|
this.setState({showFormatting: !this.state.showFormatting});
|
|
|
|
}
|
|
|
|
|
2016-09-08 00:16:56 +03:00
|
|
|
onToggleMarkdownClicked(e) {
|
|
|
|
e.preventDefault(); // don't steal focus from the editor!
|
2018-05-20 18:30:39 +03:00
|
|
|
this.messageComposerInput.enableRichtext(!this.state.inputState.isRichTextEnabled);
|
2016-09-05 15:08:53 +03:00
|
|
|
}
|
|
|
|
|
2018-08-21 17:56:56 +03:00
|
|
|
_onTombstoneClick(ev) {
|
|
|
|
ev.preventDefault();
|
|
|
|
|
|
|
|
const replacementRoomId = this.state.tombstone.getContent()['replacement_room'];
|
2019-01-11 01:29:12 +03:00
|
|
|
const replacementRoom = MatrixClientPeg.get().getRoom(replacementRoomId);
|
|
|
|
let createEventId = null;
|
|
|
|
if (replacementRoom) {
|
|
|
|
const createEvent = replacementRoom.currentState.getStateEvents('m.room.create', '');
|
|
|
|
if (createEvent && createEvent.getId()) createEventId = createEvent.getId();
|
|
|
|
}
|
2019-01-17 12:29:37 +03:00
|
|
|
dis.dispatch({
|
2018-08-21 17:56:56 +03:00
|
|
|
action: 'view_room',
|
|
|
|
highlighted: true,
|
2019-01-11 01:29:12 +03:00
|
|
|
event_id: createEventId,
|
2018-08-21 17:56:56 +03:00
|
|
|
room_id: replacementRoomId,
|
2019-01-11 00:33:46 +03:00
|
|
|
|
|
|
|
// Try to join via the server that sent the event. This converts $something:example.org
|
|
|
|
// into a server domain by splitting on colons and ignoring the first entry ("$something").
|
|
|
|
via_servers: [this.state.tombstone.getId().split(':').splice(1).join(':')],
|
2018-08-21 17:56:56 +03:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2016-06-20 11:22:55 +03:00
|
|
|
render() {
|
2017-05-17 13:31:01 +03:00
|
|
|
const uploadInputStyle = {display: 'none'};
|
2018-12-13 04:03:30 +03:00
|
|
|
const MemberStatusMessageAvatar = sdk.getComponent('avatars.MemberStatusMessageAvatar');
|
2017-07-04 16:44:55 +03:00
|
|
|
const MessageComposerInput = sdk.getComponent("rooms.MessageComposerInput");
|
2015-12-15 02:37:34 +03:00
|
|
|
|
2017-05-17 13:31:01 +03:00
|
|
|
const controls = [];
|
2016-03-24 16:57:21 +03:00
|
|
|
|
2018-08-23 01:02:20 +03:00
|
|
|
if (this.state.me) {
|
|
|
|
controls.push(
|
|
|
|
<div key="controls_avatar" className="mx_MessageComposer_avatar">
|
2018-12-13 04:03:30 +03:00
|
|
|
<MemberStatusMessageAvatar member={this.state.me} width={24} height={24} />
|
2018-08-23 01:02:20 +03:00
|
|
|
</div>,
|
|
|
|
);
|
|
|
|
}
|
2016-03-24 16:57:21 +03:00
|
|
|
|
2019-02-01 15:40:42 +03:00
|
|
|
if (this.props.e2eStatus) {
|
|
|
|
controls.push(<E2EIcon
|
|
|
|
status={this.props.e2eStatus}
|
|
|
|
key="e2eIcon"
|
|
|
|
className="mx_MessageComposer_e2eIcon" />
|
|
|
|
);
|
2016-09-12 03:37:51 +03:00
|
|
|
}
|
|
|
|
|
2018-01-12 20:38:21 +03:00
|
|
|
let callButton;
|
|
|
|
let videoCallButton;
|
|
|
|
let hangupButton;
|
|
|
|
|
2018-10-02 14:55:24 +03:00
|
|
|
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
|
2018-01-12 20:38:21 +03:00
|
|
|
// Call buttons
|
2015-12-15 02:37:34 +03:00
|
|
|
if (this.props.callState && this.props.callState !== 'ended') {
|
|
|
|
hangupButton =
|
2019-02-12 21:05:19 +03:00
|
|
|
<AccessibleButton className="mx_MessageComposer_button mx_MessageComposer_hangup"
|
|
|
|
key="controls_hangup"
|
|
|
|
onClick={this.onHangupClick}
|
|
|
|
title={_t('Hangup')}
|
|
|
|
>
|
2018-10-02 14:55:24 +03:00
|
|
|
</AccessibleButton>;
|
2017-05-17 12:58:59 +03:00
|
|
|
} else {
|
2015-12-15 02:37:34 +03:00
|
|
|
callButton =
|
2019-02-12 21:05:19 +03:00
|
|
|
<AccessibleButton className="mx_MessageComposer_button mx_MessageComposer_voicecall"
|
|
|
|
key="controls_call"
|
|
|
|
onClick={this.onVoiceCallClick}
|
|
|
|
title={_t('Voice call')}
|
|
|
|
>
|
2018-10-02 14:55:24 +03:00
|
|
|
</AccessibleButton>;
|
2015-12-15 02:37:34 +03:00
|
|
|
videoCallButton =
|
2019-02-12 21:05:19 +03:00
|
|
|
<AccessibleButton className="mx_MessageComposer_button mx_MessageComposer_videocall"
|
|
|
|
key="controls_videocall"
|
|
|
|
onClick={this.onCallClick}
|
|
|
|
title={_t('Video call')}
|
|
|
|
>
|
2018-10-02 14:55:24 +03:00
|
|
|
</AccessibleButton>;
|
2015-12-15 02:37:34 +03:00
|
|
|
}
|
|
|
|
|
2018-09-06 13:48:23 +03:00
|
|
|
const canSendMessages = !this.state.tombstone &&
|
|
|
|
this.props.room.maySendMessage();
|
2016-03-24 16:57:21 +03:00
|
|
|
|
2018-12-22 00:51:05 +03:00
|
|
|
// TODO: Remove temporary logging for riot-web#7838
|
|
|
|
// Note: we rip apart the power level event ourselves because we don't want to
|
|
|
|
// log too much data about it - just the bits we care about. Many of the variables
|
|
|
|
// logged here are to help figure out where in the stack the 'cannot post in room'
|
|
|
|
// warning is coming from. This means logging various numbers from the PL event to
|
|
|
|
// verify RoomState._maySendEventOfType is doing the right thing.
|
|
|
|
const room = this.props.room;
|
|
|
|
const plEvent = room.currentState.getStateEvents('m.room.power_levels', '');
|
|
|
|
let plEventString = "<no power level event>";
|
|
|
|
if (plEvent) {
|
|
|
|
const content = plEvent.getContent();
|
|
|
|
if (!content) {
|
|
|
|
plEventString = "<no event content>";
|
|
|
|
} else {
|
|
|
|
const stringifyFalsey = (v) => v === null ? '<null>' : (v === undefined ? '<undefined>' : v);
|
|
|
|
const actualUserPl = stringifyFalsey(content.users ? content.users[room.myUserId] : "<no users in content>");
|
|
|
|
const usersPl = stringifyFalsey(content.users_default);
|
|
|
|
const actualEventPl = stringifyFalsey(content.events ? content.events['m.room.message'] : "<no events in content>");
|
|
|
|
const eventPl = stringifyFalsey(content.events_default);
|
|
|
|
plEventString = `actualUserPl=${actualUserPl} defaultUserPl=${usersPl} actualEventPl=${actualEventPl} defaultEventPl=${eventPl}`;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
console.log(
|
|
|
|
`[riot-web#7838] renderComposer() hasTombstone=${!!this.state.tombstone} maySendMessage=${room.maySendMessage()}` +
|
|
|
|
` myMembership=${room.getMyMembership()} maySendEvent=${room.currentState.maySendEvent('m.room.message', room.myUserId)}` +
|
|
|
|
` myUserId=${room.myUserId} roomId=${room.roomId} hasPlEvent=${!!plEvent} powerLevels='${plEventString}'`
|
|
|
|
);
|
|
|
|
|
2016-03-24 16:57:21 +03:00
|
|
|
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.
|
2017-05-17 13:31:01 +03:00
|
|
|
const uploadButton = (
|
2019-02-12 21:05:19 +03:00
|
|
|
<AccessibleButton className="mx_MessageComposer_button mx_MessageComposer_upload"
|
|
|
|
key="controls_upload"
|
|
|
|
onClick={this.onUploadClick}
|
|
|
|
title={_t('Upload file')}
|
|
|
|
>
|
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} />
|
2018-10-02 14:55:24 +03:00
|
|
|
</AccessibleButton>
|
2016-03-24 16:57:21 +03:00
|
|
|
);
|
|
|
|
|
2018-05-21 00:39:40 +03:00
|
|
|
const formattingButton = this.state.inputState.isRichTextEnabled ? (
|
2018-10-02 14:55:24 +03:00
|
|
|
<AccessibleButton element="img" className="mx_MessageComposer_formatting"
|
|
|
|
alt={_t("Show Text Formatting Toolbar")}
|
2017-06-08 16:08:51 +03:00
|
|
|
title={_t("Show Text Formatting Toolbar")}
|
2019-01-11 04:37:28 +03:00
|
|
|
src={require("../../../../res/img/button-text-formatting.svg")}
|
2016-09-05 15:08:53 +03:00
|
|
|
onClick={this.onToggleFormattingClicked}
|
2017-07-04 16:44:55 +03:00
|
|
|
style={{visibility: this.state.showFormatting ? 'hidden' : 'visible'}}
|
2016-09-04 18:33:40 +03:00
|
|
|
key="controls_formatting" />
|
2018-05-21 00:39:40 +03:00
|
|
|
) : null;
|
2016-09-04 18:33:40 +03:00
|
|
|
|
2019-02-01 15:40:42 +03:00
|
|
|
const roomIsEncrypted = MatrixClientPeg.get().isRoomEncrypted(this.props.room.roomId);
|
2017-12-18 22:49:38 +03:00
|
|
|
let placeholderText;
|
|
|
|
if (this.state.isQuoting) {
|
|
|
|
if (roomIsEncrypted) {
|
2018-01-10 15:52:20 +03:00
|
|
|
placeholderText = _t('Send an encrypted reply…');
|
2017-12-18 22:49:38 +03:00
|
|
|
} else {
|
2018-01-10 15:52:20 +03:00
|
|
|
placeholderText = _t('Send a reply (unencrypted)…');
|
2017-12-18 22:49:38 +03:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (roomIsEncrypted) {
|
2018-01-10 15:52:20 +03:00
|
|
|
placeholderText = _t('Send an encrypted message…');
|
2017-12-18 22:49:38 +03:00
|
|
|
} else {
|
2018-01-10 15:52:20 +03:00
|
|
|
placeholderText = _t('Send a message (unencrypted)…');
|
2017-12-18 22:49:38 +03:00
|
|
|
}
|
|
|
|
}
|
2017-02-21 18:33:44 +03:00
|
|
|
|
2018-05-09 16:59:35 +03:00
|
|
|
const stickerpickerButton = <Stickerpicker key='stickerpicker_controls_button' room={this.props.room} />;
|
2018-04-03 00:24:46 +03:00
|
|
|
|
2016-03-24 16:57:21 +03:00
|
|
|
controls.push(
|
2016-06-21 16:03:39 +03:00
|
|
|
<MessageComposerInput
|
2017-05-17 13:31:01 +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}
|
2017-02-21 18:33:44 +03:00
|
|
|
placeholder={placeholderText}
|
2017-05-17 03:41:42 +03:00
|
|
|
onFilesPasted={this.uploadFiles}
|
2016-09-05 15:08:53 +03:00
|
|
|
onInputStateChanged={this.onInputStateChanged} />,
|
2016-09-04 18:33:40 +03:00
|
|
|
formattingButton,
|
2018-04-03 00:24:46 +03:00
|
|
|
stickerpickerButton,
|
2016-03-24 16:57:21 +03:00
|
|
|
uploadButton,
|
|
|
|
hangupButton,
|
|
|
|
callButton,
|
2017-05-17 12:58:59 +03:00
|
|
|
videoCallButton,
|
2016-03-24 16:57:21 +03:00
|
|
|
);
|
2018-08-21 17:56:56 +03:00
|
|
|
} else if (this.state.tombstone) {
|
|
|
|
const replacementRoomId = this.state.tombstone.getContent()['replacement_room'];
|
|
|
|
|
|
|
|
controls.push(<div className="mx_MessageComposer_replaced_wrapper">
|
|
|
|
<div className="mx_MessageComposer_replaced_valign">
|
2019-01-11 04:37:28 +03:00
|
|
|
<img className="mx_MessageComposer_roomReplaced_icon" src={require("../../../../res/img/room_replaced.svg")} />
|
2018-08-21 17:56:56 +03:00
|
|
|
<span className="mx_MessageComposer_roomReplaced_header">
|
|
|
|
{_t("This room has been replaced and is no longer active.")}
|
|
|
|
</span><br />
|
|
|
|
<a href={makeRoomPermalink(replacementRoomId)}
|
|
|
|
className="mx_MessageComposer_roomReplaced_link"
|
|
|
|
onClick={this._onTombstoneClick}
|
|
|
|
>
|
|
|
|
{_t("The conversation continues here.")}
|
|
|
|
</a>
|
|
|
|
</div>
|
|
|
|
</div>);
|
2016-03-24 16:57:21 +03:00
|
|
|
} else {
|
2018-12-22 00:51:05 +03:00
|
|
|
// TODO: Remove temporary logging for riot-web#7838
|
|
|
|
console.log("[riot-web#7838] Falling back to showing cannot post in room error");
|
2016-03-24 16:57:21 +03:00
|
|
|
controls.push(
|
2016-04-13 04:02:55 +03:00
|
|
|
<div key="controls_error" className="mx_MessageComposer_noperm_error">
|
2017-05-23 17:16:31 +03:00
|
|
|
{ _t('You do not have permission to post to this room') }
|
2017-06-12 16:52:41 +03:00
|
|
|
</div>,
|
2016-03-24 16:57:21 +03:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2018-05-21 00:39:40 +03:00
|
|
|
let formatBar;
|
2018-05-21 00:48:40 +03:00
|
|
|
if (this.state.showFormatting && this.state.inputState.isRichTextEnabled) {
|
2018-05-21 00:39:40 +03:00
|
|
|
const {marks, blockType} = this.state.inputState;
|
2018-07-11 13:39:55 +03:00
|
|
|
const formatButtons = formatButtonList.map((name) => {
|
2018-07-17 15:24:28 +03:00
|
|
|
// special-case to match the md serializer and the special-case in MessageComposerInput.js
|
|
|
|
const markName = name === 'inline-code' ? 'code' : name;
|
|
|
|
const active = marks.some(mark => mark.type === markName) || blockType === name;
|
2018-07-11 13:39:55 +03:00
|
|
|
const suffix = active ? '-on' : '';
|
|
|
|
const onFormatButtonClicked = this.onFormatButtonClicked.bind(this, name);
|
|
|
|
const className = 'mx_MessageComposer_format_button mx_filterFlipColor';
|
|
|
|
return <img className={className}
|
|
|
|
title={_t(name)}
|
|
|
|
onMouseDown={onFormatButtonClicked}
|
|
|
|
key={name}
|
2019-01-11 04:37:28 +03:00
|
|
|
src={require(`../../../../res/img/button-text-${name}${suffix}.svg`)}
|
2018-07-11 13:39:55 +03:00
|
|
|
height="17" />;
|
2018-05-21 00:39:40 +03:00
|
|
|
},
|
|
|
|
);
|
2016-09-04 18:33:40 +03:00
|
|
|
|
2018-05-21 00:39:40 +03:00
|
|
|
formatBar =
|
2017-07-04 16:44:55 +03:00
|
|
|
<div className="mx_MessageComposer_formatbar_wrapper">
|
2018-05-21 00:39:40 +03:00
|
|
|
<div className="mx_MessageComposer_formatbar">
|
2017-10-11 19:56:17 +03:00
|
|
|
{ formatButtons }
|
2017-07-04 16:44:55 +03:00
|
|
|
<div style={{flex: 1}}></div>
|
2018-05-20 18:30:39 +03:00
|
|
|
<img title={this.state.inputState.isRichTextEnabled ? _t("Turn Markdown on") : _t("Turn Markdown off")}
|
2017-07-04 16:44:55 +03:00
|
|
|
onMouseDown={this.onToggleMarkdownClicked}
|
|
|
|
className="mx_MessageComposer_formatbar_markdown mx_filterFlipColor"
|
2019-01-11 04:37:28 +03:00
|
|
|
src={require(`../../../../res/img/button-md-${!this.state.inputState.isRichTextEnabled}.png`)} />
|
2018-10-02 14:55:24 +03:00
|
|
|
<AccessibleButton element="img" title={_t("Hide Text Formatting Toolbar")}
|
2017-07-04 16:44:55 +03:00
|
|
|
onClick={this.onToggleFormattingClicked}
|
|
|
|
className="mx_MessageComposer_formatbar_cancel mx_filterFlipColor"
|
2019-01-11 04:37:28 +03:00
|
|
|
src={require("../../../../res/img/icon-text-cancel.svg")} />
|
2017-07-04 16:44:55 +03:00
|
|
|
</div>
|
2018-10-27 06:50:35 +03:00
|
|
|
</div>;
|
2018-05-21 00:39:40 +03:00
|
|
|
}
|
|
|
|
|
2019-02-01 15:40:42 +03:00
|
|
|
const wrapperClasses = classNames({
|
|
|
|
mx_MessageComposer_wrapper: true,
|
|
|
|
mx_MessageComposer_hasE2EIcon: !!this.props.e2eStatus,
|
|
|
|
});
|
2018-05-21 00:39:40 +03:00
|
|
|
return (
|
|
|
|
<div className="mx_MessageComposer">
|
2019-02-01 15:40:42 +03:00
|
|
|
<div className={wrapperClasses}>
|
2018-05-21 00:39:40 +03:00
|
|
|
<div className="mx_MessageComposer_row">
|
|
|
|
{ controls }
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
{ formatBar }
|
2015-11-26 20:31:10 +03:00
|
|
|
</div>
|
|
|
|
);
|
2015-09-18 12:44:57 +03:00
|
|
|
}
|
2017-01-20 17:22:27 +03:00
|
|
|
}
|
2016-06-20 11:22:55 +03:00
|
|
|
|
|
|
|
MessageComposer.propTypes = {
|
|
|
|
// a callback which is called when the height of the composer is
|
|
|
|
// changed due to a change in content.
|
2017-12-26 04:03:18 +03:00
|
|
|
onResize: PropTypes.func,
|
2016-06-20 11:22:55 +03:00
|
|
|
|
|
|
|
// js-sdk Room object
|
2017-12-26 04:03:18 +03:00
|
|
|
room: PropTypes.object.isRequired,
|
2016-06-20 11:22:55 +03:00
|
|
|
|
|
|
|
// string representing the current voip call state
|
2017-12-26 04:03:18 +03:00
|
|
|
callState: PropTypes.string,
|
2016-06-20 11:22:55 +03:00
|
|
|
|
|
|
|
// callback when a file to upload is chosen
|
2017-12-26 04:03:18 +03:00
|
|
|
uploadFile: PropTypes.func.isRequired,
|
2015-06-15 20:35:28 +03:00
|
|
|
|
2018-06-23 16:28:20 +03:00
|
|
|
// function to test whether a file should be allowed to be uploaded.
|
|
|
|
uploadAllowed: PropTypes.func.isRequired,
|
2018-06-21 12:08:41 +03:00
|
|
|
|
2017-05-17 13:31:01 +03:00
|
|
|
// string representing the current room app drawer state
|
2019-01-17 12:29:37 +03:00
|
|
|
showApps: PropTypes.bool
|
2016-06-20 11:22:55 +03:00
|
|
|
};
|