diff --git a/src/app/features/room/RoomInput.tsx b/src/app/features/room/RoomInput.tsx index ae46d2d098..698ab6d847 100644 --- a/src/app/features/room/RoomInput.tsx +++ b/src/app/features/room/RoomInput.tsx @@ -9,7 +9,7 @@ import React, { } from 'react'; import { useAtom, useAtomValue } from 'jotai'; import { isKeyHotkey } from 'is-hotkey'; -import { EventType, IContent, MsgType, RelationType, Room } from 'matrix-js-sdk'; +import { EventType, IContent, IEventRelation, MsgType, RelationType, Room } from 'matrix-js-sdk'; import { ReactEditor } from 'slate-react'; import { Transforms, Editor } from 'slate'; import { @@ -29,6 +29,7 @@ import { toRem, } from 'folds'; +import { StickerEventContent } from 'matrix-js-sdk/lib/types'; import { useMatrixClient } from '../../hooks/useMatrixClient'; import { CustomEditor, @@ -68,6 +69,7 @@ import { useFilePicker } from '../../hooks/useFilePicker'; import { useFilePasteHandler } from '../../hooks/useFilePasteHandler'; import { useFileDropZone } from '../../hooks/useFileDrop'; import { + IReplyDraft, TUploadItem, TUploadMetadata, roomIdToMsgDraftAtomFamily, @@ -118,6 +120,26 @@ import { useRoomCreatorsTag } from '../../hooks/useRoomCreatorsTag'; import { usePowerLevelTags } from '../../hooks/usePowerLevelTags'; import { useComposingCheck } from '../../hooks/useComposingCheck'; +const getReplyContent = (replyDraft: IReplyDraft | undefined): IEventRelation => { + if (!replyDraft) return {}; + + const relatesTo: IEventRelation = {}; + + relatesTo['m.in_reply_to'] = { + event_id: replyDraft.eventId, + }; + + if (replyDraft.relation?.rel_type === RelationType.Thread) { + relatesTo.event_id = replyDraft.relation.event_id; + relatesTo.rel_type = RelationType.Thread; + relatesTo.is_falling_back = false; + } + return relatesTo; +}; +interface ReplyEventContent { + 'm.relates_to'?: IEventRelation; +} + interface RoomInputProps { editor: Editor; fileDropContainerRef: RefObject; @@ -276,6 +298,7 @@ export const RoomInput = forwardRef( }; const handleSendUpload = async (uploads: UploadSuccess[]) => { + const plaintext = toPlainText(editor.children, isMarkdown).trim(); const contentsPromises = uploads.map(async (upload) => { const fileItem = selectedFiles.find((f) => f.file === upload.file); if (!fileItem) throw new Error('Broken upload'); @@ -293,6 +316,13 @@ export const RoomInput = forwardRef( }); handleCancelUpload(uploads); const contents = fulfilledPromiseSettledResult(await Promise.allSettled(contentsPromises)); + + if (contents.length > 0) { + const replyContent = plaintext.length === 0 ? getReplyContent(replyDraft) : undefined; + if (replyContent) contents[0]['m.relates_to'] = replyContent; + setReplyDraft(undefined); + } + contents.forEach((content) => mx.sendMessage(roomId, content as any)); }; @@ -360,18 +390,7 @@ export const RoomInput = forwardRef( content.format = 'org.matrix.custom.html'; content.formatted_body = formattedBody; } - if (replyDraft) { - content['m.relates_to'] = { - 'm.in_reply_to': { - event_id: replyDraft.eventId, - }, - }; - if (replyDraft.relation?.rel_type === RelationType.Thread) { - content['m.relates_to'].event_id = replyDraft.relation.event_id; - content['m.relates_to'].rel_type = RelationType.Thread; - content['m.relates_to'].is_falling_back = false; - } - } + if (replyDraft) content['m.relates_to'] = getReplyContent(replyDraft); mx.sendMessage(roomId, content as any); resetEditor(editor); resetEditorHistory(editor); @@ -439,11 +458,16 @@ export const RoomInput = forwardRef( await getImageUrlBlob(stickerUrl) ); - mx.sendEvent(roomId, EventType.Sticker, { + const content: StickerEventContent & ReplyEventContent = { body: label, url: mxc, info, - }); + }; + if (replyDraft) { + content['m.relates_to'] = getReplyContent(replyDraft); + setReplyDraft(undefined); + } + mx.sendEvent(roomId, EventType.Sticker, content); }; return ( diff --git a/src/app/features/room/RoomTimeline.tsx b/src/app/features/room/RoomTimeline.tsx index d1678b65b3..fa5f22c5c3 100644 --- a/src/app/features/room/RoomTimeline.tsx +++ b/src/app/features/room/RoomTimeline.tsx @@ -1235,6 +1235,7 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli const reactionRelations = getEventReactions(timelineSet, mEventId); const reactions = reactionRelations && reactionRelations.getSortedAnnotationsByKey(); const hasReactions = reactions && reactions.length > 0; + const { replyEventId, threadRootId } = mEvent; const highlighted = focusItem?.index === item && focusItem.highlight; return ( @@ -1257,6 +1258,20 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli onUsernameClick={handleUsernameClick} onReplyClick={handleReplyClick} onReactionToggle={handleReactionToggle} + reply={ + replyEventId && ( + + ) + } reactions={ reactionRelations && (