2015-06-23 18:41:25 +03:00
|
|
|
/*
|
2021-02-26 23:50:03 +03:00
|
|
|
Copyright 2015-2018, 2020, 2021 The Matrix.org Foundation C.I.C.
|
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.
|
|
|
|
*/
|
2019-12-08 15:16:17 +03:00
|
|
|
import React, {createRef} from 'react';
|
2020-07-28 15:19:11 +03:00
|
|
|
import classNames from 'classnames';
|
2017-12-26 04:03:18 +03:00
|
|
|
import PropTypes from 'prop-types';
|
2019-08-06 18:03:44 +03:00
|
|
|
import { _t } from '../../../languageHandler';
|
2019-12-21 00:13:46 +03:00
|
|
|
import {MatrixClientPeg} from '../../../MatrixClientPeg';
|
2019-12-20 04:19:56 +03:00
|
|
|
import * as sdk from '../../../index';
|
2020-05-14 05:41:41 +03:00
|
|
|
import dis from '../../../dispatcher/dispatcher';
|
2018-02-26 01:10:38 +03:00
|
|
|
import Stickerpicker from './Stickerpicker';
|
2019-10-01 05:39:58 +03:00
|
|
|
import { makeRoomPermalink } from '../../../utils/permalinks/Permalinks';
|
2019-04-01 18:42:41 +03:00
|
|
|
import ContentMessages from '../../../ContentMessages';
|
2019-02-01 15:40:42 +03:00
|
|
|
import E2EIcon from './E2EIcon';
|
2020-01-23 17:38:39 +03:00
|
|
|
import SettingsStore from "../../../settings/SettingsStore";
|
2020-07-17 20:16:31 +03:00
|
|
|
import {aboveLeftOf, ContextMenu, ContextMenuTooltipButton, useContextMenu} from "../../structures/ContextMenu";
|
|
|
|
import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";
|
2020-07-31 19:27:07 +03:00
|
|
|
import ReplyPreview from "./ReplyPreview";
|
2020-09-16 13:38:50 +03:00
|
|
|
import {UIFeature} from "../../../settings/UIFeature";
|
2020-09-16 23:35:50 +03:00
|
|
|
import WidgetStore from "../../../stores/WidgetStore";
|
|
|
|
import {UPDATE_EVENT} from "../../../stores/AsyncStore";
|
|
|
|
import ActiveWidgetStore from "../../../stores/ActiveWidgetStore";
|
2021-03-09 06:12:00 +03:00
|
|
|
import {replaceableComponent} from "../../../utils/replaceableComponent";
|
2021-03-16 07:16:58 +03:00
|
|
|
import VoiceRecordComposerTile from "./VoiceRecordComposerTile";
|
2021-04-15 05:11:34 +03:00
|
|
|
import {VoiceRecordingStore} from "../../../stores/VoiceRecordingStore";
|
2015-06-15 20:35:28 +03:00
|
|
|
|
2019-04-13 11:44:36 +03:00
|
|
|
function ComposerAvatar(props) {
|
2019-04-07 06:44:22 +03:00
|
|
|
const MemberStatusMessageAvatar = sdk.getComponent('avatars.MemberStatusMessageAvatar');
|
|
|
|
return <div className="mx_MessageComposer_avatar">
|
|
|
|
<MemberStatusMessageAvatar member={props.me} width={24} height={24} />
|
|
|
|
</div>;
|
|
|
|
}
|
|
|
|
|
2019-04-13 11:44:36 +03:00
|
|
|
ComposerAvatar.propTypes = {
|
2019-04-07 06:44:22 +03:00
|
|
|
me: PropTypes.object.isRequired,
|
2019-08-05 16:50:16 +03:00
|
|
|
};
|
2019-04-07 06:44:22 +03:00
|
|
|
|
2021-02-12 17:35:04 +03:00
|
|
|
function SendButton(props) {
|
|
|
|
return (
|
2021-02-12 17:52:42 +03:00
|
|
|
<AccessibleTooltipButton
|
2021-02-12 19:06:02 +03:00
|
|
|
className="mx_MessageComposer_sendMessage"
|
2021-02-12 17:52:42 +03:00
|
|
|
onClick={props.onClick}
|
|
|
|
title={_t('Send message')}
|
|
|
|
/>
|
2021-02-12 17:35:04 +03:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
SendButton.propTypes = {
|
|
|
|
onClick: PropTypes.func.isRequired,
|
|
|
|
};
|
|
|
|
|
2019-12-19 12:25:51 +03:00
|
|
|
const EmojiButton = ({addEmoji}) => {
|
|
|
|
const [menuDisplayed, button, openMenu, closeMenu] = useContextMenu();
|
|
|
|
|
|
|
|
let contextMenu;
|
|
|
|
if (menuDisplayed) {
|
|
|
|
const buttonRect = button.current.getBoundingClientRect();
|
|
|
|
const EmojiPicker = sdk.getComponent('emojipicker.EmojiPicker');
|
|
|
|
contextMenu = <ContextMenu {...aboveLeftOf(buttonRect)} onFinished={closeMenu} catchTab={false}>
|
|
|
|
<EmojiPicker onChoose={addEmoji} showQuickReactions={true} />
|
|
|
|
</ContextMenu>;
|
|
|
|
}
|
|
|
|
|
2020-07-28 15:19:11 +03:00
|
|
|
const className = classNames(
|
|
|
|
"mx_MessageComposer_button",
|
|
|
|
"mx_MessageComposer_emoji",
|
|
|
|
{
|
2020-07-28 19:13:58 +03:00
|
|
|
"mx_MessageComposer_button_highlight": menuDisplayed,
|
2020-07-28 15:19:11 +03:00
|
|
|
},
|
|
|
|
);
|
|
|
|
|
2020-07-28 19:13:58 +03:00
|
|
|
// TODO: replace ContextMenuTooltipButton with a unified representation of
|
|
|
|
// the header buttons and the right panel buttons
|
2019-12-19 12:25:51 +03:00
|
|
|
return <React.Fragment>
|
2020-07-17 20:16:31 +03:00
|
|
|
<ContextMenuTooltipButton
|
2020-07-28 15:19:11 +03:00
|
|
|
className={className}
|
2020-07-17 20:16:31 +03:00
|
|
|
onClick={openMenu}
|
|
|
|
isExpanded={menuDisplayed}
|
|
|
|
title={_t('Emoji picker')}
|
|
|
|
inputRef={button}
|
2019-12-19 12:25:51 +03:00
|
|
|
>
|
|
|
|
|
2020-07-17 20:16:31 +03:00
|
|
|
</ContextMenuTooltipButton>
|
2019-12-19 12:25:51 +03:00
|
|
|
|
|
|
|
{ contextMenu }
|
|
|
|
</React.Fragment>;
|
|
|
|
};
|
|
|
|
|
2019-04-07 07:14:29 +03:00
|
|
|
class UploadButton extends React.Component {
|
|
|
|
static propTypes = {
|
|
|
|
roomId: PropTypes.string.isRequired,
|
|
|
|
}
|
2019-08-06 18:03:44 +03:00
|
|
|
|
2019-12-17 20:26:12 +03:00
|
|
|
constructor(props) {
|
|
|
|
super(props);
|
2019-04-07 07:14:29 +03:00
|
|
|
this.onUploadClick = this.onUploadClick.bind(this);
|
|
|
|
this.onUploadFileInputChange = this.onUploadFileInputChange.bind(this);
|
2019-12-08 15:16:17 +03:00
|
|
|
|
|
|
|
this._uploadInput = createRef();
|
2020-05-13 12:38:32 +03:00
|
|
|
this._dispatcherRef = dis.register(this.onAction);
|
2019-04-07 07:14:29 +03:00
|
|
|
}
|
|
|
|
|
2020-05-13 12:38:32 +03:00
|
|
|
componentWillUnmount() {
|
|
|
|
dis.unregister(this._dispatcherRef);
|
|
|
|
}
|
|
|
|
|
|
|
|
onAction = payload => {
|
|
|
|
if (payload.action === "upload_file") {
|
|
|
|
this.onUploadClick();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2019-04-07 07:14:29 +03:00
|
|
|
onUploadClick(ev) {
|
|
|
|
if (MatrixClientPeg.get().isGuest()) {
|
|
|
|
dis.dispatch({action: 'require_registration'});
|
|
|
|
return;
|
|
|
|
}
|
2019-12-08 15:16:17 +03:00
|
|
|
this._uploadInput.current.click();
|
2019-04-07 07:14:29 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
onUploadFileInputChange(ev) {
|
|
|
|
if (ev.target.files.length === 0) return;
|
|
|
|
|
|
|
|
// take a copy so we can safely reset the value of the form control
|
2020-05-13 12:38:32 +03:00
|
|
|
// (Note it is a FileList: we can't use slice or sensible iteration).
|
2019-04-07 07:14:29 +03:00
|
|
|
const tfiles = [];
|
|
|
|
for (let i = 0; i < ev.target.files.length; ++i) {
|
|
|
|
tfiles.push(ev.target.files[i]);
|
|
|
|
}
|
|
|
|
|
|
|
|
ContentMessages.sharedInstance().sendContentListToRoom(
|
|
|
|
tfiles, this.props.roomId, MatrixClientPeg.get(),
|
|
|
|
);
|
|
|
|
|
|
|
|
// This is the onChange handler for a file form control, but we're
|
|
|
|
// not keeping any state, so reset the value of the form control
|
|
|
|
// to empty.
|
|
|
|
// NB. we need to set 'value': the 'files' property is immutable.
|
|
|
|
ev.target.value = '';
|
|
|
|
}
|
|
|
|
|
|
|
|
render() {
|
|
|
|
const uploadInputStyle = {display: 'none'};
|
|
|
|
return (
|
2020-07-17 20:16:31 +03:00
|
|
|
<AccessibleTooltipButton
|
|
|
|
className="mx_MessageComposer_button mx_MessageComposer_upload"
|
2019-04-07 07:14:29 +03:00
|
|
|
onClick={this.onUploadClick}
|
|
|
|
title={_t('Upload file')}
|
|
|
|
>
|
2019-12-08 15:16:17 +03:00
|
|
|
<input
|
|
|
|
ref={this._uploadInput}
|
|
|
|
type="file"
|
2019-04-07 07:14:29 +03:00
|
|
|
style={uploadInputStyle}
|
|
|
|
multiple
|
|
|
|
onChange={this.onUploadFileInputChange}
|
|
|
|
/>
|
2020-07-17 20:16:31 +03:00
|
|
|
</AccessibleTooltipButton>
|
2019-04-07 07:14:29 +03:00
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
2019-04-07 06:37:23 +03:00
|
|
|
|
2021-03-09 06:12:00 +03:00
|
|
|
@replaceableComponent("views.rooms.MessageComposer")
|
2016-06-20 11:22:55 +03:00
|
|
|
export default class MessageComposer extends React.Component {
|
2019-12-17 20:26:12 +03:00
|
|
|
constructor(props) {
|
|
|
|
super(props);
|
2016-09-05 15:08:53 +03:00
|
|
|
this.onInputStateChanged = this.onInputStateChanged.bind(this);
|
2018-08-21 17:56:56 +03:00
|
|
|
this._onRoomStateEvents = this._onRoomStateEvents.bind(this);
|
|
|
|
this._onTombstoneClick = this._onTombstoneClick.bind(this);
|
2019-04-07 06:57:45 +03:00
|
|
|
this.renderPlaceholderText = this.renderPlaceholderText.bind(this);
|
2020-09-16 23:35:50 +03:00
|
|
|
WidgetStore.instance.on(UPDATE_EVENT, this._onWidgetUpdate);
|
|
|
|
ActiveWidgetStore.on('update', this._onActiveWidgetUpdate);
|
2021-04-15 05:11:34 +03:00
|
|
|
VoiceRecordingStore.instance.on(UPDATE_EVENT, this._onVoiceStoreUpdate);
|
2020-07-31 19:27:07 +03:00
|
|
|
this._dispatcherRef = null;
|
2020-09-16 23:35:50 +03:00
|
|
|
|
2016-06-20 11:22:55 +03:00
|
|
|
this.state = {
|
2018-08-21 17:56:56 +03:00
|
|
|
tombstone: this._getRoomTombstone(),
|
2019-02-27 03:23:37 +03:00
|
|
|
canSendMessages: this.props.room.maySendMessage(),
|
2020-09-16 23:35:50 +03:00
|
|
|
hasConference: WidgetStore.instance.doesRoomHaveConference(this.props.room),
|
|
|
|
joinedConference: WidgetStore.instance.isJoinedToConferenceIn(this.props.room),
|
2021-02-12 17:41:43 +03:00
|
|
|
isComposerEmpty: true,
|
2021-03-16 07:16:58 +03:00
|
|
|
haveRecording: false,
|
2016-06-01 14:24:21 +03:00
|
|
|
};
|
2016-06-20 11:22:55 +03:00
|
|
|
}
|
2016-06-01 14:24:21 +03:00
|
|
|
|
2020-07-31 19:27:07 +03:00
|
|
|
onAction = (payload) => {
|
|
|
|
if (payload.action === 'reply_to_event') {
|
|
|
|
// add a timeout for the reply preview to be rendered, so
|
|
|
|
// that the ScrollPanel listening to the resizeNotifier can
|
|
|
|
// correctly measure it's new height and scroll down to keep
|
|
|
|
// at the bottom if it already is
|
|
|
|
setTimeout(() => {
|
|
|
|
this.props.resizeNotifier.notifyTimelineHeightChanged();
|
|
|
|
}, 100);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2020-09-16 23:35:50 +03:00
|
|
|
_onWidgetUpdate = () => {
|
|
|
|
this.setState({hasConference: WidgetStore.instance.doesRoomHaveConference(this.props.room)});
|
|
|
|
};
|
|
|
|
|
|
|
|
_onActiveWidgetUpdate = () => {
|
|
|
|
this.setState({joinedConference: WidgetStore.instance.isJoinedToConferenceIn(this.props.room)});
|
|
|
|
};
|
|
|
|
|
2016-09-15 21:25:53 +03:00
|
|
|
componentDidMount() {
|
2020-07-31 19:27:07 +03:00
|
|
|
this.dispatcherRef = dis.register(this.onAction);
|
2018-08-21 17:56:56 +03:00
|
|
|
MatrixClientPeg.get().on("RoomState.events", this._onRoomStateEvents);
|
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()) {
|
2018-08-21 17:56:56 +03:00
|
|
|
MatrixClientPeg.get().removeListener("RoomState.events", this._onRoomStateEvents);
|
2016-09-29 18:57:10 +03:00
|
|
|
}
|
2020-09-16 23:35:50 +03:00
|
|
|
WidgetStore.instance.removeListener(UPDATE_EVENT, this._onWidgetUpdate);
|
|
|
|
ActiveWidgetStore.removeListener('update', this._onActiveWidgetUpdate);
|
2021-04-15 05:11:34 +03:00
|
|
|
VoiceRecordingStore.instance.off(UPDATE_EVENT, this._onVoiceStoreUpdate);
|
2020-08-18 13:34:43 +03:00
|
|
|
dis.unregister(this.dispatcherRef);
|
2016-09-15 21:25:53 +03:00
|
|
|
}
|
|
|
|
|
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()});
|
|
|
|
}
|
2019-02-27 03:23:37 +03:00
|
|
|
if (ev.getType() === 'm.room.power_levels') {
|
|
|
|
this.setState({canSendMessages: this.props.room.maySendMessage()});
|
|
|
|
}
|
2018-08-21 17:56:56 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
_getRoomTombstone() {
|
|
|
|
return this.props.room.currentState.getStateEvents('m.room.tombstone', '');
|
|
|
|
}
|
|
|
|
|
2016-09-05 15:08:53 +03:00
|
|
|
onInputStateChanged(inputState) {
|
2019-02-22 18:12:28 +03:00
|
|
|
// Merge the new input state with old to support partial updates
|
|
|
|
inputState = Object.assign({}, this.state.inputState, inputState);
|
2016-09-05 15:08:53 +03:00
|
|
|
this.setState({inputState});
|
|
|
|
}
|
|
|
|
|
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-07-11 17:39:41 +03:00
|
|
|
|
|
|
|
const viaServers = [this.state.tombstone.getSender().split(':').splice(1).join(':')];
|
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-07-11 17:39:41 +03:00
|
|
|
auto_join: true,
|
2020-10-29 18:53:14 +03:00
|
|
|
_type: "tombstone", // instrumentation
|
2019-01-11 00:33:46 +03:00
|
|
|
|
2019-06-28 21:34: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").
|
2019-07-11 17:39:41 +03:00
|
|
|
via_servers: viaServers,
|
|
|
|
opts: {
|
|
|
|
// These are passed down to the js-sdk's /join call
|
|
|
|
viaServers: viaServers,
|
|
|
|
},
|
2018-08-21 17:56:56 +03:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2019-04-07 06:57:45 +03:00
|
|
|
renderPlaceholderText() {
|
2020-10-28 15:47:59 +03:00
|
|
|
if (this.props.replyToEvent) {
|
2020-05-27 12:28:25 +03:00
|
|
|
if (this.props.e2eStatus) {
|
|
|
|
return _t('Send an encrypted reply…');
|
2019-04-07 06:57:45 +03:00
|
|
|
} else {
|
2020-05-27 12:28:25 +03:00
|
|
|
return _t('Send a reply…');
|
2019-04-07 06:57:45 +03:00
|
|
|
}
|
|
|
|
} else {
|
2020-05-27 12:28:25 +03:00
|
|
|
if (this.props.e2eStatus) {
|
|
|
|
return _t('Send an encrypted message…');
|
2019-04-07 06:57:45 +03:00
|
|
|
} else {
|
2020-05-27 12:28:25 +03:00
|
|
|
return _t('Send a message…');
|
2019-04-07 06:57:45 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-12-19 12:25:51 +03:00
|
|
|
addEmoji(emoji) {
|
|
|
|
dis.dispatch({
|
|
|
|
action: "insert_emoji",
|
|
|
|
emoji,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2021-01-09 11:09:14 +03:00
|
|
|
sendMessage = () => {
|
|
|
|
this.messageComposerInput._sendMessage();
|
|
|
|
}
|
|
|
|
|
2021-02-17 15:32:48 +03:00
|
|
|
onChange = (model) => {
|
2021-02-12 17:39:54 +03:00
|
|
|
this.setState({
|
2021-02-17 15:32:48 +03:00
|
|
|
isComposerEmpty: model.isEmpty,
|
2021-02-12 17:39:54 +03:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2021-04-15 05:11:34 +03:00
|
|
|
_onVoiceStoreUpdate = () => {
|
|
|
|
this.setState({haveRecording: !!VoiceRecordingStore.instance.activeRecording});
|
2021-03-16 07:16:58 +03:00
|
|
|
};
|
|
|
|
|
2016-06-20 11:22:55 +03:00
|
|
|
render() {
|
2019-04-07 06:44:22 +03:00
|
|
|
const controls = [
|
2019-04-13 11:44:36 +03:00
|
|
|
this.state.me ? <ComposerAvatar key="controls_avatar" me={this.state.me} /> : null,
|
2019-08-05 16:50:16 +03:00
|
|
|
this.props.e2eStatus ?
|
|
|
|
<E2EIcon key="e2eIcon" status={this.props.e2eStatus} className="mx_MessageComposer_e2eIcon" /> :
|
|
|
|
null,
|
2019-04-07 06:44:22 +03:00
|
|
|
];
|
2016-09-12 03:37:51 +03:00
|
|
|
|
2019-02-27 03:23:37 +03:00
|
|
|
if (!this.state.tombstone && this.state.canSendMessages) {
|
2019-08-06 18:03:44 +03:00
|
|
|
const SendMessageComposer = sdk.getComponent("rooms.SendMessageComposer");
|
2018-04-03 00:24:46 +03:00
|
|
|
|
2016-03-24 16:57:21 +03:00
|
|
|
controls.push(
|
2019-08-06 18:03:44 +03:00
|
|
|
<SendMessageComposer
|
2017-05-17 13:31:01 +03:00
|
|
|
ref={(c) => this.messageComposerInput = c}
|
2016-06-21 16:03:39 +03:00
|
|
|
key="controls_input"
|
|
|
|
room={this.props.room}
|
2019-04-07 06:57:45 +03:00
|
|
|
placeholder={this.renderPlaceholderText()}
|
2020-07-31 15:02:40 +03:00
|
|
|
resizeNotifier={this.props.resizeNotifier}
|
2020-10-06 16:47:53 +03:00
|
|
|
permalinkCreator={this.props.permalinkCreator}
|
2020-10-28 15:47:59 +03:00
|
|
|
replyToEvent={this.props.replyToEvent}
|
2021-02-17 15:25:53 +03:00
|
|
|
onChange={this.onChange}
|
2021-03-16 07:16:58 +03:00
|
|
|
disabled={this.state.haveRecording}
|
2021-03-17 08:57:27 +03:00
|
|
|
/>,
|
2016-03-24 16:57:21 +03:00
|
|
|
);
|
2020-03-17 14:33:10 +03:00
|
|
|
|
2021-03-16 07:16:58 +03:00
|
|
|
if (!this.state.haveRecording) {
|
|
|
|
controls.push(
|
|
|
|
<UploadButton key="controls_upload" roomId={this.props.room.roomId} />,
|
|
|
|
<EmojiButton key="emoji_button" addEmoji={this.addEmoji} />,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2021-01-03 00:31:49 +03:00
|
|
|
if (SettingsStore.getValue(UIFeature.Widgets) &&
|
2021-03-16 07:16:58 +03:00
|
|
|
SettingsStore.getValue("MessageComposerInput.showStickersButton") &&
|
|
|
|
!this.state.haveRecording) {
|
2020-09-16 13:38:50 +03:00
|
|
|
controls.push(<Stickerpicker key="stickerpicker_controls_button" room={this.props.room} />);
|
|
|
|
}
|
|
|
|
|
2021-03-16 07:16:58 +03:00
|
|
|
if (SettingsStore.getValue("feature_voice_messages")) {
|
|
|
|
controls.push(<VoiceRecordComposerTile
|
|
|
|
key="controls_voice_record"
|
2021-04-15 05:11:34 +03:00
|
|
|
room={this.props.room} />);
|
2021-03-16 07:16:58 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
if (!this.state.isComposerEmpty || this.state.haveRecording) {
|
2021-02-12 17:35:04 +03:00
|
|
|
controls.push(
|
2021-02-17 15:22:19 +03:00
|
|
|
<SendButton key="controls_send" onClick={this.sendMessage} />,
|
2021-02-12 17:35:04 +03:00
|
|
|
);
|
2021-01-09 11:09:14 +03:00
|
|
|
}
|
2018-08-21 17:56:56 +03:00
|
|
|
} else if (this.state.tombstone) {
|
|
|
|
const replacementRoomId = this.state.tombstone.getContent()['replacement_room'];
|
|
|
|
|
2019-04-03 23:12:36 +03:00
|
|
|
const continuesLink = replacementRoomId ? (
|
|
|
|
<a href={makeRoomPermalink(replacementRoomId)}
|
|
|
|
className="mx_MessageComposer_roomReplaced_link"
|
|
|
|
onClick={this._onTombstoneClick}
|
|
|
|
>
|
|
|
|
{_t("The conversation continues here.")}
|
|
|
|
</a>
|
|
|
|
) : '';
|
|
|
|
|
2020-02-23 17:07:50 +03:00
|
|
|
controls.push(<div className="mx_MessageComposer_replaced_wrapper" key="room_replaced">
|
2018-08-21 17:56:56 +03:00
|
|
|
<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 />
|
2019-04-03 23:12:36 +03:00
|
|
|
{ continuesLink }
|
2018-08-21 17:56:56 +03:00
|
|
|
</div>
|
|
|
|
</div>);
|
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">
|
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
|
|
|
return (
|
2020-05-06 11:24:33 +03:00
|
|
|
<div className="mx_MessageComposer mx_GroupLayout">
|
2019-11-20 21:00:39 +03:00
|
|
|
<div className="mx_MessageComposer_wrapper">
|
2020-07-31 19:27:07 +03:00
|
|
|
<ReplyPreview permalinkCreator={this.props.permalinkCreator} />
|
2018-05-21 00:39:40 +03:00
|
|
|
<div className="mx_MessageComposer_row">
|
|
|
|
{ controls }
|
|
|
|
</div>
|
|
|
|
</div>
|
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 = {
|
|
|
|
// 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
|
|
|
|
2017-05-17 13:31:01 +03:00
|
|
|
// string representing the current room app drawer state
|
2019-08-05 16:50:16 +03:00
|
|
|
showApps: PropTypes.bool,
|
2016-06-20 11:22:55 +03:00
|
|
|
};
|