2015-06-23 16:41:25 +01:00
|
|
|
/*
|
2021-04-06 12:26:50 +01:00
|
|
|
Copyright 2015-2021 The Matrix.org Foundation C.I.C.
|
2015-06-23 16:41:25 +01: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.
|
|
|
|
|
*/
|
2022-02-09 14:42:08 +00:00
|
|
|
|
2021-12-21 09:37:06 +00:00
|
|
|
import React, { createRef } from 'react';
|
2020-07-28 13:19:11 +01:00
|
|
|
import classNames from 'classnames';
|
2021-10-14 16:57:02 +01:00
|
|
|
import { MatrixEvent, IEventRelation } from "matrix-js-sdk/src/models/event";
|
2021-05-26 14:14:55 +01:00
|
|
|
import { Room } from "matrix-js-sdk/src/models/room";
|
|
|
|
|
import { RoomMember } from "matrix-js-sdk/src/models/room-member";
|
2021-12-09 09:10:23 +00:00
|
|
|
import { RelationType } from 'matrix-js-sdk/src/@types/event';
|
|
|
|
|
|
|
|
|
|
import { _t } from '../../../languageHandler';
|
|
|
|
|
import { MatrixClientPeg } from '../../../MatrixClientPeg';
|
2020-05-13 20:41:41 -06:00
|
|
|
import dis from '../../../dispatcher/dispatcher';
|
2021-05-26 14:14:55 +01:00
|
|
|
import { ActionPayload } from "../../../dispatcher/payloads";
|
2018-02-25 22:10:38 +00:00
|
|
|
import Stickerpicker from './Stickerpicker';
|
2021-05-26 14:14:55 +01:00
|
|
|
import { makeRoomPermalink, RoomPermalinkCreator } from '../../../utils/permalinks/Permalinks';
|
2019-02-01 13:40:42 +01:00
|
|
|
import E2EIcon from './E2EIcon';
|
2020-01-23 14:38:39 +00:00
|
|
|
import SettingsStore from "../../../settings/SettingsStore";
|
2022-01-28 15:44:03 +00:00
|
|
|
import { aboveLeftOf, AboveLeftOf } from "../../structures/ContextMenu";
|
2020-07-17 18:16:31 +01:00
|
|
|
import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";
|
2020-07-31 18:27:07 +02:00
|
|
|
import ReplyPreview from "./ReplyPreview";
|
2021-05-26 14:14:55 +01:00
|
|
|
import { UPDATE_EVENT } from "../../../stores/AsyncStore";
|
|
|
|
|
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
2021-03-15 22:16:58 -06:00
|
|
|
import VoiceRecordComposerTile from "./VoiceRecordComposerTile";
|
2021-05-26 14:14:55 +01:00
|
|
|
import { VoiceRecordingStore } from "../../../stores/VoiceRecordingStore";
|
2021-07-22 09:26:26 -06:00
|
|
|
import { RecordingState } from "../../../audio/VoiceRecording";
|
2021-05-26 14:14:55 +01:00
|
|
|
import Tooltip, { Alignment } from "../elements/Tooltip";
|
2021-04-06 12:26:50 +01:00
|
|
|
import ResizeNotifier from "../../../utils/ResizeNotifier";
|
2021-05-26 14:14:55 +01:00
|
|
|
import { E2EStatus } from '../../../utils/ShieldUtils';
|
2021-10-01 15:35:54 +02:00
|
|
|
import SendMessageComposer, { SendMessageComposer as SendMessageComposerClass } from "./SendMessageComposer";
|
2021-05-26 14:14:55 +01:00
|
|
|
import { ComposerInsertPayload } from "../../../dispatcher/payloads/ComposerInsertPayload";
|
|
|
|
|
import { Action } from "../../../dispatcher/actions";
|
2021-06-30 13:01:26 +01:00
|
|
|
import EditorModel from "../../../editor/model";
|
2021-09-06 22:11:35 -06:00
|
|
|
import UIStore, { UI_EVENTS } from '../../../stores/UIStore';
|
2021-10-19 16:05:34 +01:00
|
|
|
import RoomContext from '../../../contexts/RoomContext';
|
2021-12-02 14:09:57 +00:00
|
|
|
import { SettingUpdatedPayload } from "../../../dispatcher/payloads/SettingUpdatedPayload";
|
2022-01-28 15:44:03 +00:00
|
|
|
import MessageComposerButtons from './MessageComposerButtons';
|
2022-02-09 14:42:08 +00:00
|
|
|
import { ButtonEvent } from '../elements/AccessibleButton';
|
2022-02-10 14:29:55 +00:00
|
|
|
import { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload";
|
2021-09-06 22:11:35 -06:00
|
|
|
|
2021-09-07 16:02:26 +01:00
|
|
|
let instanceCount = 0;
|
2021-09-06 22:11:35 -06:00
|
|
|
const NARROW_MODE_BREAKPOINT = 500;
|
2015-06-15 18:35:28 +01:00
|
|
|
|
2021-04-06 12:26:50 +01:00
|
|
|
interface ISendButtonProps {
|
2022-02-09 14:42:08 +00:00
|
|
|
onClick: (ev: ButtonEvent) => void;
|
2021-08-05 12:38:15 -06:00
|
|
|
title?: string; // defaults to something generic
|
2021-04-06 12:26:50 +01:00
|
|
|
}
|
2019-04-06 20:44:22 -07:00
|
|
|
|
2021-04-06 12:26:50 +01:00
|
|
|
function SendButton(props: ISendButtonProps) {
|
2021-02-12 15:35:04 +01:00
|
|
|
return (
|
2021-02-12 15:52:42 +01:00
|
|
|
<AccessibleTooltipButton
|
2021-02-12 17:06:02 +01:00
|
|
|
className="mx_MessageComposer_sendMessage"
|
2021-02-12 15:52:42 +01:00
|
|
|
onClick={props.onClick}
|
2021-08-05 12:38:15 -06:00
|
|
|
title={props.title ?? _t('Send message')}
|
2021-02-12 15:52:42 +01:00
|
|
|
/>
|
2021-02-12 15:35:04 +01:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2021-04-06 12:26:50 +01:00
|
|
|
interface IProps {
|
|
|
|
|
room: Room;
|
|
|
|
|
resizeNotifier: ResizeNotifier;
|
|
|
|
|
permalinkCreator: RoomPermalinkCreator;
|
|
|
|
|
replyToEvent?: MatrixEvent;
|
2021-10-14 16:57:02 +01:00
|
|
|
relation?: IEventRelation;
|
2021-04-06 12:26:50 +01:00
|
|
|
e2eStatus?: E2EStatus;
|
2021-08-20 12:11:04 +01:00
|
|
|
compact?: boolean;
|
2021-04-06 12:26:50 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
interface IState {
|
|
|
|
|
tombstone: MatrixEvent;
|
|
|
|
|
canSendMessages: boolean;
|
|
|
|
|
isComposerEmpty: boolean;
|
|
|
|
|
haveRecording: boolean;
|
|
|
|
|
recordingTimeLeftSeconds?: number;
|
|
|
|
|
me?: RoomMember;
|
2021-09-06 22:11:35 -06:00
|
|
|
narrowMode?: boolean;
|
|
|
|
|
isMenuOpen: boolean;
|
2022-01-28 10:58:58 +00:00
|
|
|
isStickerPickerOpen: boolean;
|
2021-12-02 14:09:57 +00:00
|
|
|
showStickersButton: boolean;
|
2021-04-06 12:26:50 +01:00
|
|
|
}
|
|
|
|
|
|
2021-03-08 20:12:00 -07:00
|
|
|
@replaceableComponent("views.rooms.MessageComposer")
|
2021-04-06 12:26:50 +01:00
|
|
|
export default class MessageComposer extends React.Component<IProps, IState> {
|
|
|
|
|
private dispatcherRef: string;
|
2021-10-01 15:35:54 +02:00
|
|
|
private messageComposerInput = createRef<SendMessageComposerClass>();
|
|
|
|
|
private voiceRecordingButton = createRef<VoiceRecordComposerTile>();
|
2021-09-06 22:11:35 -06:00
|
|
|
private ref: React.RefObject<HTMLDivElement> = createRef();
|
2021-09-07 16:02:26 +01:00
|
|
|
private instanceId: number;
|
2021-04-06 12:26:50 +01:00
|
|
|
|
2021-10-25 13:43:54 +02:00
|
|
|
static contextType = RoomContext;
|
|
|
|
|
public context!: React.ContextType<typeof RoomContext>;
|
2021-10-19 16:05:34 +01:00
|
|
|
|
2021-08-17 11:10:02 +01:00
|
|
|
static defaultProps = {
|
2021-08-20 12:11:04 +01:00
|
|
|
compact: false,
|
2021-08-17 11:10:02 +01:00
|
|
|
};
|
|
|
|
|
|
2022-01-28 09:39:10 +00:00
|
|
|
constructor(props: IProps) {
|
2019-12-17 17:26:12 +00:00
|
|
|
super(props);
|
2021-04-26 14:02:53 +01:00
|
|
|
VoiceRecordingStore.instance.on(UPDATE_EVENT, this.onVoiceStoreUpdate);
|
2020-09-16 14:35:50 -06:00
|
|
|
|
2016-06-20 13:52:55 +05:30
|
|
|
this.state = {
|
2021-04-06 12:26:50 +01:00
|
|
|
tombstone: this.getRoomTombstone(),
|
2019-02-27 00:23:37 +00:00
|
|
|
canSendMessages: this.props.room.maySendMessage(),
|
2021-02-12 15:41:43 +01:00
|
|
|
isComposerEmpty: true,
|
2021-03-15 22:16:58 -06:00
|
|
|
haveRecording: false,
|
2021-04-14 21:15:06 -06:00
|
|
|
recordingTimeLeftSeconds: null, // when set to a number, shows a toast
|
2021-09-06 22:11:35 -06:00
|
|
|
isMenuOpen: false,
|
2022-01-28 10:58:58 +00:00
|
|
|
isStickerPickerOpen: false,
|
2021-12-02 14:09:57 +00:00
|
|
|
showStickersButton: SettingsStore.getValue("MessageComposerInput.showStickersButton"),
|
2016-06-01 16:54:21 +05:30
|
|
|
};
|
2021-09-07 16:02:26 +01:00
|
|
|
|
|
|
|
|
this.instanceId = instanceCount++;
|
2021-12-02 14:09:57 +00:00
|
|
|
|
|
|
|
|
SettingsStore.monitorSetting("MessageComposerInput.showStickersButton", null);
|
2016-06-20 13:52:55 +05:30
|
|
|
}
|
2016-06-01 16:54:21 +05:30
|
|
|
|
2021-04-06 12:26:50 +01:00
|
|
|
componentDidMount() {
|
|
|
|
|
this.dispatcherRef = dis.register(this.onAction);
|
|
|
|
|
MatrixClientPeg.get().on("RoomState.events", this.onRoomStateEvents);
|
|
|
|
|
this.waitForOwnMember();
|
2021-09-07 16:02:26 +01:00
|
|
|
UIStore.instance.trackElementDimensions(`MessageComposer${this.instanceId}`, this.ref.current);
|
|
|
|
|
UIStore.instance.on(`MessageComposer${this.instanceId}`, this.onResize);
|
2021-04-06 12:26:50 +01:00
|
|
|
}
|
|
|
|
|
|
2021-09-06 22:11:35 -06:00
|
|
|
private onResize = (type: UI_EVENTS, entry: ResizeObserverEntry) => {
|
|
|
|
|
if (type === UI_EVENTS.Resize) {
|
|
|
|
|
const narrowMode = entry.contentRect.width <= NARROW_MODE_BREAKPOINT;
|
|
|
|
|
this.setState({
|
|
|
|
|
narrowMode,
|
|
|
|
|
isMenuOpen: !narrowMode ? false : this.state.isMenuOpen,
|
2022-01-28 10:58:58 +00:00
|
|
|
isStickerPickerOpen: false,
|
2021-09-06 22:11:35 -06:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2021-04-26 15:21:49 +01:00
|
|
|
private onAction = (payload: ActionPayload) => {
|
2021-12-02 14:09:57 +00:00
|
|
|
switch (payload.action) {
|
|
|
|
|
case "reply_to_event":
|
|
|
|
|
if (payload.context === this.context.timelineRenderingType) {
|
|
|
|
|
// 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);
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case Action.SettingUpdated: {
|
|
|
|
|
const settingUpdatedPayload = payload as SettingUpdatedPayload;
|
|
|
|
|
switch (settingUpdatedPayload.settingName) {
|
|
|
|
|
case "MessageComposerInput.showStickersButton": {
|
|
|
|
|
const showStickersButton = SettingsStore.getValue("MessageComposerInput.showStickersButton");
|
|
|
|
|
if (this.state.showStickersButton !== showStickersButton) {
|
|
|
|
|
this.setState({ showStickersButton });
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-07-31 18:27:07 +02:00
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2021-04-06 12:26:50 +01:00
|
|
|
private waitForOwnMember() {
|
2018-08-23 00:02:20 +02:00
|
|
|
// if we have the member already, do that
|
|
|
|
|
const me = this.props.room.getMember(MatrixClientPeg.get().getUserId());
|
|
|
|
|
if (me) {
|
2021-06-29 13:11:58 +01:00
|
|
|
this.setState({ me });
|
2018-08-23 00:02:20 +02:00
|
|
|
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());
|
2021-06-29 13:11:58 +01:00
|
|
|
this.setState({ me });
|
2018-08-23 00:02:20 +02:00
|
|
|
});
|
2016-09-15 19:25:53 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
componentWillUnmount() {
|
2016-09-29 16:57:10 +01:00
|
|
|
if (MatrixClientPeg.get()) {
|
2021-04-06 12:26:50 +01:00
|
|
|
MatrixClientPeg.get().removeListener("RoomState.events", this.onRoomStateEvents);
|
2016-09-29 16:57:10 +01:00
|
|
|
}
|
2021-04-26 14:02:53 +01:00
|
|
|
VoiceRecordingStore.instance.off(UPDATE_EVENT, this.onVoiceStoreUpdate);
|
2020-08-18 12:34:43 +02:00
|
|
|
dis.unregister(this.dispatcherRef);
|
2021-09-07 16:02:26 +01:00
|
|
|
UIStore.instance.stopTrackingElementDimensions(`MessageComposer${this.instanceId}`);
|
|
|
|
|
UIStore.instance.removeListener(`MessageComposer${this.instanceId}`, this.onResize);
|
2016-09-15 19:25:53 +01:00
|
|
|
}
|
|
|
|
|
|
2021-04-06 12:26:50 +01:00
|
|
|
private onRoomStateEvents = (ev, state) => {
|
2018-08-21 15:56:56 +01:00
|
|
|
if (ev.getRoomId() !== this.props.room.roomId) return;
|
|
|
|
|
|
|
|
|
|
if (ev.getType() === 'm.room.tombstone') {
|
2021-06-29 13:11:58 +01:00
|
|
|
this.setState({ tombstone: this.getRoomTombstone() });
|
2018-08-21 15:56:56 +01:00
|
|
|
}
|
2019-02-27 00:23:37 +00:00
|
|
|
if (ev.getType() === 'm.room.power_levels') {
|
2021-06-29 13:11:58 +01:00
|
|
|
this.setState({ canSendMessages: this.props.room.maySendMessage() });
|
2019-02-27 00:23:37 +00:00
|
|
|
}
|
2021-06-29 13:11:58 +01:00
|
|
|
};
|
2018-08-21 15:56:56 +01:00
|
|
|
|
2021-04-06 12:26:50 +01:00
|
|
|
private getRoomTombstone() {
|
2018-08-21 15:56:56 +01:00
|
|
|
return this.props.room.currentState.getStateEvents('m.room.tombstone', '');
|
|
|
|
|
}
|
|
|
|
|
|
2021-04-06 12:26:50 +01:00
|
|
|
private onTombstoneClick = (ev) => {
|
2018-08-21 15:56:56 +01:00
|
|
|
ev.preventDefault();
|
|
|
|
|
|
|
|
|
|
const replacementRoomId = this.state.tombstone.getContent()['replacement_room'];
|
2019-01-10 15:29:12 -07: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 08:39:41 -06:00
|
|
|
|
2021-11-18 13:50:37 -07:00
|
|
|
const viaServers = [this.state.tombstone.getSender().split(':').slice(1).join(':')];
|
2022-02-10 14:29:55 +00:00
|
|
|
dis.dispatch<ViewRoomPayload>({
|
2021-11-25 17:49:43 -03:00
|
|
|
action: Action.ViewRoom,
|
2018-08-21 15:56:56 +01:00
|
|
|
highlighted: true,
|
2019-01-10 15:29:12 -07:00
|
|
|
event_id: createEventId,
|
2018-08-21 15:56:56 +01:00
|
|
|
room_id: replacementRoomId,
|
2019-07-11 08:39:41 -06:00
|
|
|
auto_join: true,
|
2019-06-28 12:34:46 -06: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 08:39:41 -06:00
|
|
|
via_servers: viaServers,
|
2022-02-10 14:29:55 +00:00
|
|
|
_trigger: "Tombstone",
|
|
|
|
|
_viaKeyboard: ev.type !== "click",
|
2018-08-21 15:56:56 +01:00
|
|
|
});
|
2021-06-29 13:11:58 +01:00
|
|
|
};
|
2018-08-21 15:56:56 +01:00
|
|
|
|
2021-04-06 12:26:50 +01:00
|
|
|
private renderPlaceholderText = () => {
|
2020-10-28 12:47:59 +00:00
|
|
|
if (this.props.replyToEvent) {
|
2021-10-14 16:57:02 +01:00
|
|
|
const replyingToThread = this.props.relation?.rel_type === RelationType.Thread;
|
|
|
|
|
if (replyingToThread && this.props.e2eStatus) {
|
2021-09-07 16:02:26 +01:00
|
|
|
return _t('Reply to encrypted thread…');
|
2021-10-14 16:57:02 +01:00
|
|
|
} else if (replyingToThread) {
|
2021-09-07 16:02:26 +01:00
|
|
|
return _t('Reply to thread…');
|
|
|
|
|
} else if (this.props.e2eStatus) {
|
2020-05-27 10:28:25 +01:00
|
|
|
return _t('Send an encrypted reply…');
|
2019-04-06 20:57:45 -07:00
|
|
|
} else {
|
2020-05-27 10:28:25 +01:00
|
|
|
return _t('Send a reply…');
|
2019-04-06 20:57:45 -07:00
|
|
|
}
|
|
|
|
|
} else {
|
2020-05-27 10:28:25 +01:00
|
|
|
if (this.props.e2eStatus) {
|
|
|
|
|
return _t('Send an encrypted message…');
|
2019-04-06 20:57:45 -07:00
|
|
|
} else {
|
2020-05-27 10:28:25 +01:00
|
|
|
return _t('Send a message…');
|
2019-04-06 20:57:45 -07:00
|
|
|
}
|
|
|
|
|
}
|
2021-06-29 13:11:58 +01:00
|
|
|
};
|
2019-04-06 20:57:45 -07:00
|
|
|
|
2021-10-25 13:43:54 +02:00
|
|
|
private addEmoji = (emoji: string): boolean => {
|
2021-05-24 22:02:50 +01:00
|
|
|
dis.dispatch<ComposerInsertPayload>({
|
|
|
|
|
action: Action.ComposerInsert,
|
2021-04-13 15:09:37 +01:00
|
|
|
text: emoji,
|
2021-10-25 13:43:54 +02:00
|
|
|
timelineRenderingType: this.context.timelineRenderingType,
|
2019-12-19 09:25:51 +00:00
|
|
|
});
|
2021-09-07 17:10:09 +01:00
|
|
|
return true;
|
2021-10-25 13:43:54 +02:00
|
|
|
};
|
2019-12-19 09:25:51 +00:00
|
|
|
|
2021-06-30 13:01:26 +01:00
|
|
|
private sendMessage = async () => {
|
2021-10-01 15:35:54 +02:00
|
|
|
if (this.state.haveRecording && this.voiceRecordingButton.current) {
|
2021-04-27 18:59:10 -06:00
|
|
|
// There shouldn't be any text message to send when a voice recording is active, so
|
|
|
|
|
// just send out the voice recording.
|
2021-10-01 15:35:54 +02:00
|
|
|
await this.voiceRecordingButton.current?.send();
|
2021-04-27 18:59:10 -06:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2021-10-01 15:35:54 +02:00
|
|
|
this.messageComposerInput.current?.sendMessage();
|
2021-06-29 13:11:58 +01:00
|
|
|
};
|
2021-01-09 09:09:14 +01:00
|
|
|
|
2021-06-30 13:01:26 +01:00
|
|
|
private onChange = (model: EditorModel) => {
|
2021-02-12 15:39:54 +01:00
|
|
|
this.setState({
|
2021-02-17 13:32:48 +01:00
|
|
|
isComposerEmpty: model.isEmpty,
|
2021-02-12 15:39:54 +01:00
|
|
|
});
|
2021-06-29 13:11:58 +01:00
|
|
|
};
|
2021-02-12 15:39:54 +01:00
|
|
|
|
2021-04-26 14:02:53 +01:00
|
|
|
private onVoiceStoreUpdate = () => {
|
2021-04-14 21:15:06 -06:00
|
|
|
const recording = VoiceRecordingStore.instance.activeRecording;
|
|
|
|
|
if (recording) {
|
2021-07-26 23:33:21 -06:00
|
|
|
// Delay saying we have a recording until it is started, as we might not yet have A/V permissions
|
|
|
|
|
recording.on(RecordingState.Started, () => {
|
|
|
|
|
this.setState({ haveRecording: !!VoiceRecordingStore.instance.activeRecording });
|
|
|
|
|
});
|
2021-04-16 10:11:04 -06:00
|
|
|
// We show a little heads up that the recording is about to automatically end soon. The 3s
|
2021-04-14 21:15:06 -06:00
|
|
|
// display time is completely arbitrary. Note that we don't need to deregister the listener
|
|
|
|
|
// because the recording instance will clean that up for us.
|
2021-06-29 13:11:58 +01:00
|
|
|
recording.on(RecordingState.EndingSoon, ({ secondsLeft }) => {
|
|
|
|
|
this.setState({ recordingTimeLeftSeconds: secondsLeft });
|
|
|
|
|
setTimeout(() => this.setState({ recordingTimeLeftSeconds: null }), 3000);
|
2021-04-14 21:15:06 -06:00
|
|
|
});
|
2021-07-26 23:33:21 -06:00
|
|
|
} else {
|
|
|
|
|
this.setState({ haveRecording: false });
|
2021-04-14 21:15:06 -06:00
|
|
|
}
|
2021-03-15 22:16:58 -06:00
|
|
|
};
|
|
|
|
|
|
2022-01-28 10:58:58 +00:00
|
|
|
private setStickerPickerOpen = (isStickerPickerOpen: boolean) => {
|
2022-01-31 16:05:05 +00:00
|
|
|
this.setState({
|
|
|
|
|
isStickerPickerOpen,
|
|
|
|
|
isMenuOpen: false,
|
|
|
|
|
});
|
2021-09-06 22:11:35 -06:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
private toggleButtonMenu = (): void => {
|
|
|
|
|
this.setState({
|
|
|
|
|
isMenuOpen: !this.state.isMenuOpen,
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
2016-06-20 13:52:55 +05:30
|
|
|
render() {
|
2019-04-06 20:44:22 -07:00
|
|
|
const controls = [
|
2019-08-05 15:50:16 +02:00
|
|
|
this.props.e2eStatus ?
|
|
|
|
|
<E2EIcon key="e2eIcon" status={this.props.e2eStatus} className="mx_MessageComposer_e2eIcon" /> :
|
|
|
|
|
null,
|
2019-04-06 20:44:22 -07:00
|
|
|
];
|
2016-09-12 01:37:51 +01:00
|
|
|
|
2021-09-13 22:11:43 +02:00
|
|
|
let menuPosition: AboveLeftOf | undefined;
|
2021-09-06 22:11:35 -06:00
|
|
|
if (this.ref.current) {
|
|
|
|
|
const contentRect = this.ref.current.getBoundingClientRect();
|
|
|
|
|
menuPosition = aboveLeftOf(contentRect);
|
|
|
|
|
}
|
|
|
|
|
|
2019-02-27 00:23:37 +00:00
|
|
|
if (!this.state.tombstone && this.state.canSendMessages) {
|
2016-03-24 13:57:21 +00:00
|
|
|
controls.push(
|
2019-08-06 17:03:44 +02:00
|
|
|
<SendMessageComposer
|
2021-10-01 15:35:54 +02:00
|
|
|
ref={this.messageComposerInput}
|
2016-06-21 18:33:39 +05:30
|
|
|
key="controls_input"
|
|
|
|
|
room={this.props.room}
|
2019-04-06 20:57:45 -07:00
|
|
|
placeholder={this.renderPlaceholderText()}
|
2020-10-06 14:47:53 +01:00
|
|
|
permalinkCreator={this.props.permalinkCreator}
|
2021-10-14 16:57:02 +01:00
|
|
|
relation={this.props.relation}
|
2020-10-28 12:47:59 +00:00
|
|
|
replyToEvent={this.props.replyToEvent}
|
2021-02-17 13:25:53 +01:00
|
|
|
onChange={this.onChange}
|
2021-03-15 22:16:58 -06:00
|
|
|
disabled={this.state.haveRecording}
|
2021-03-16 23:57:27 -06:00
|
|
|
/>,
|
2016-03-24 13:57:21 +00:00
|
|
|
);
|
2020-03-17 11:33:10 +00:00
|
|
|
|
2021-07-21 18:09:35 -06:00
|
|
|
controls.push(<VoiceRecordComposerTile
|
|
|
|
|
key="controls_voice_record"
|
2021-10-01 15:35:54 +02:00
|
|
|
ref={this.voiceRecordingButton}
|
2021-07-21 18:09:35 -06:00
|
|
|
room={this.props.room} />);
|
2018-08-21 15:56:56 +01:00
|
|
|
} else if (this.state.tombstone) {
|
|
|
|
|
const replacementRoomId = this.state.tombstone.getContent()['replacement_room'];
|
|
|
|
|
|
2019-04-03 21:12:36 +01:00
|
|
|
const continuesLink = replacementRoomId ? (
|
|
|
|
|
<a href={makeRoomPermalink(replacementRoomId)}
|
|
|
|
|
className="mx_MessageComposer_roomReplaced_link"
|
2021-04-06 12:26:50 +01:00
|
|
|
onClick={this.onTombstoneClick}
|
2019-04-03 21:12:36 +01:00
|
|
|
>
|
2021-07-19 22:43:11 +01:00
|
|
|
{ _t("The conversation continues here.") }
|
2019-04-03 21:12:36 +01:00
|
|
|
</a>
|
|
|
|
|
) : '';
|
|
|
|
|
|
2020-02-23 14:07:50 +00:00
|
|
|
controls.push(<div className="mx_MessageComposer_replaced_wrapper" key="room_replaced">
|
2018-08-21 15:56:56 +01:00
|
|
|
<div className="mx_MessageComposer_replaced_valign">
|
2021-04-01 14:15:21 +01:00
|
|
|
<img className="mx_MessageComposer_roomReplaced_icon"
|
|
|
|
|
src={require("../../../../res/img/room_replaced.svg")}
|
|
|
|
|
/>
|
2018-08-21 15:56:56 +01:00
|
|
|
<span className="mx_MessageComposer_roomReplaced_header">
|
2021-07-19 22:43:11 +01:00
|
|
|
{ _t("This room has been replaced and is no longer active.") }
|
2018-08-21 15:56:56 +01:00
|
|
|
</span><br />
|
2019-04-03 21:12:36 +01:00
|
|
|
{ continuesLink }
|
2018-08-21 15:56:56 +01:00
|
|
|
</div>
|
|
|
|
|
</div>);
|
2016-03-24 13:57:21 +00:00
|
|
|
} else {
|
|
|
|
|
controls.push(
|
2016-04-13 02:02:55 +01:00
|
|
|
<div key="controls_error" className="mx_MessageComposer_noperm_error">
|
2017-05-23 15:16:31 +01:00
|
|
|
{ _t('You do not have permission to post to this room') }
|
2017-06-12 14:52:41 +01:00
|
|
|
</div>,
|
2016-03-24 13:57:21 +00:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2021-04-14 21:15:06 -06:00
|
|
|
let recordingTooltip;
|
|
|
|
|
const secondsLeft = Math.round(this.state.recordingTimeLeftSeconds);
|
|
|
|
|
if (secondsLeft) {
|
|
|
|
|
recordingTooltip = <Tooltip
|
2021-06-29 13:11:58 +01:00
|
|
|
label={_t("%(seconds)ss left", { seconds: secondsLeft })}
|
2021-07-23 10:23:45 +01:00
|
|
|
alignment={Alignment.Top}
|
|
|
|
|
yOffset={-50}
|
2021-04-14 21:15:06 -06:00
|
|
|
/>;
|
|
|
|
|
}
|
2021-12-03 08:22:13 +00:00
|
|
|
|
|
|
|
|
const threadId = this.props.relation?.rel_type === RelationType.Thread
|
|
|
|
|
? this.props.relation.event_id
|
|
|
|
|
: null;
|
|
|
|
|
|
2021-09-06 22:11:35 -06:00
|
|
|
controls.push(
|
|
|
|
|
<Stickerpicker
|
|
|
|
|
room={this.props.room}
|
2021-12-03 08:22:13 +00:00
|
|
|
threadId={threadId}
|
2022-01-28 10:58:58 +00:00
|
|
|
isStickerPickerOpen={this.state.isStickerPickerOpen}
|
|
|
|
|
setStickerPickerOpen={this.setStickerPickerOpen}
|
2021-10-18 13:47:42 -06:00
|
|
|
menuPosition={menuPosition}
|
|
|
|
|
key="stickers"
|
|
|
|
|
/>,
|
2021-09-06 22:11:35 -06:00
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const showSendButton = !this.state.isComposerEmpty || this.state.haveRecording;
|
2021-08-25 15:36:50 +01:00
|
|
|
|
2021-08-20 12:11:04 +01:00
|
|
|
const classes = classNames({
|
|
|
|
|
"mx_MessageComposer": true,
|
|
|
|
|
"mx_GroupLayout": true,
|
|
|
|
|
"mx_MessageComposer--compact": this.props.compact,
|
2021-11-02 13:18:51 +00:00
|
|
|
"mx_MessageComposer_e2eStatus": this.props.e2eStatus != undefined,
|
2021-08-20 12:11:04 +01:00
|
|
|
});
|
|
|
|
|
|
2018-05-20 22:39:40 +01:00
|
|
|
return (
|
2021-09-06 22:11:35 -06:00
|
|
|
<div className={classes} ref={this.ref}>
|
2021-07-19 22:43:11 +01:00
|
|
|
{ recordingTooltip }
|
2019-11-20 11:00:39 -07:00
|
|
|
<div className="mx_MessageComposer_wrapper">
|
2021-10-19 16:05:34 +01:00
|
|
|
<ReplyPreview
|
|
|
|
|
replyToEvent={this.props.replyToEvent}
|
|
|
|
|
permalinkCreator={this.props.permalinkCreator} />
|
2018-05-20 22:39:40 +01:00
|
|
|
<div className="mx_MessageComposer_row">
|
|
|
|
|
{ controls }
|
2022-02-04 10:36:31 +01:00
|
|
|
{ this.state.canSendMessages && <MessageComposerButtons
|
2022-01-28 15:44:03 +00:00
|
|
|
addEmoji={this.addEmoji}
|
|
|
|
|
haveRecording={this.state.haveRecording}
|
|
|
|
|
isMenuOpen={this.state.isMenuOpen}
|
|
|
|
|
isStickerPickerOpen={this.state.isStickerPickerOpen}
|
|
|
|
|
menuPosition={menuPosition}
|
|
|
|
|
narrowMode={this.state.narrowMode}
|
|
|
|
|
relation={this.props.relation}
|
2022-01-31 16:05:05 +00:00
|
|
|
onRecordStartEndClick={() => {
|
|
|
|
|
this.voiceRecordingButton.current?.onRecordStartEndClick();
|
|
|
|
|
if (this.state.narrowMode) {
|
|
|
|
|
this.toggleButtonMenu();
|
|
|
|
|
}
|
|
|
|
|
}}
|
2022-01-28 15:44:03 +00:00
|
|
|
setStickerPickerOpen={this.setStickerPickerOpen}
|
2022-02-02 11:44:17 +00:00
|
|
|
showLocationButton={!window.electron}
|
2022-01-28 15:44:03 +00:00
|
|
|
showStickersButton={this.state.showStickersButton}
|
|
|
|
|
toggleButtonMenu={this.toggleButtonMenu}
|
2022-02-04 10:36:31 +01:00
|
|
|
/> }
|
2021-09-06 22:11:35 -06:00
|
|
|
{ showSendButton && (
|
|
|
|
|
<SendButton
|
|
|
|
|
key="controls_send"
|
|
|
|
|
onClick={this.sendMessage}
|
|
|
|
|
title={this.state.haveRecording ? _t("Send voice message") : undefined}
|
|
|
|
|
/>
|
|
|
|
|
) }
|
2018-05-20 22:39:40 +01:00
|
|
|
</div>
|
|
|
|
|
</div>
|
2015-11-26 17:31:10 +00:00
|
|
|
</div>
|
|
|
|
|
);
|
2015-09-18 10:44:57 +01:00
|
|
|
}
|
2017-01-20 14:22:27 +00:00
|
|
|
}
|