From c4266fbc22baa80da8ea4c4d66bc9d0f650ffc0e Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Tue, 22 Oct 2024 19:53:10 +0300 Subject: [PATCH] web/composer: send thread message when replying in thread --- pkg/hicli/json-commands.go | 4 ++-- pkg/hicli/send.go | 21 ++++++++++++++++++--- web/src/api/rpc.ts | 3 ++- web/src/api/types/mxtypes.ts | 12 ++++++++++++ web/src/ui/composer/MessageComposer.tsx | 17 +++++++++++++++-- 5 files changed, 49 insertions(+), 8 deletions(-) diff --git a/pkg/hicli/json-commands.go b/pkg/hicli/json-commands.go index 0f92914..7025ccd 100644 --- a/pkg/hicli/json-commands.go +++ b/pkg/hicli/json-commands.go @@ -41,7 +41,7 @@ func (h *HiClient) handleJSONCommand(ctx context.Context, req *JSONCommand) (any }) case "send_message": return unmarshalAndCall(req.Data, func(params *sendMessageParams) (*database.Event, error) { - return h.SendMessage(ctx, params.RoomID, params.BaseContent, params.Text, params.ReplyTo, params.Mentions) + return h.SendMessage(ctx, params.RoomID, params.BaseContent, params.Text, params.RelatesTo, params.Mentions) }) case "send_event": return unmarshalAndCall(req.Data, func(params *sendEventParams) (*database.Event, error) { @@ -122,7 +122,7 @@ type sendMessageParams struct { RoomID id.RoomID `json:"room_id"` BaseContent *event.MessageEventContent `json:"base_content"` Text string `json:"text"` - ReplyTo id.EventID `json:"reply_to"` + RelatesTo *event.RelatesTo `json:"relates_to"` Mentions *event.Mentions `json:"mentions"` } diff --git a/pkg/hicli/send.go b/pkg/hicli/send.go index 058601e..0b7b16f 100644 --- a/pkg/hicli/send.go +++ b/pkg/hicli/send.go @@ -32,7 +32,14 @@ var ( rainbowWithHTML = goldmark.New(format.Extensions, format.HTMLOptions, goldmark.WithExtensions(rainbow.Extension)) ) -func (h *HiClient) SendMessage(ctx context.Context, roomID id.RoomID, base *event.MessageEventContent, text string, replyTo id.EventID, mentions *event.Mentions) (*database.Event, error) { +func (h *HiClient) SendMessage( + ctx context.Context, + roomID id.RoomID, + base *event.MessageEventContent, + text string, + relatesTo *event.RelatesTo, + mentions *event.Mentions, +) (*database.Event, error) { var content event.MessageEventContent if strings.HasPrefix(text, "/rainbow ") { text = strings.TrimPrefix(text, "/rainbow ") @@ -66,8 +73,16 @@ func (h *HiClient) SendMessage(ctx context.Context, roomID id.RoomID, base *even } } } - if replyTo != "" { - content.RelatesTo = (&event.RelatesTo{}).SetReplyTo(replyTo) + if relatesTo != nil { + if relatesTo.Type == event.RelReplace { + contentCopy := content + content = event.MessageEventContent{ + NewContent: &contentCopy, + RelatesTo: relatesTo, + } + } else { + content.RelatesTo = relatesTo + } } return h.Send(ctx, roomID, event.EventMessage, &content) } diff --git a/web/src/api/rpc.ts b/web/src/api/rpc.ts index 9d41eb8..916f824 100644 --- a/web/src/api/rpc.ts +++ b/web/src/api/rpc.ts @@ -27,6 +27,7 @@ import type { RPCEvent, RawDBEvent, ReceiptType, + RelatesTo, ResolveAliasResponse, RoomAlias, RoomID, @@ -50,7 +51,7 @@ export interface SendMessageParams { base_content?: MessageEventContent text: string media_path?: string - reply_to?: EventID + relates_to?: RelatesTo mentions?: Mentions } diff --git a/web/src/api/types/mxtypes.ts b/web/src/api/types/mxtypes.ts index 959242e..90a427a 100644 --- a/web/src/api/types/mxtypes.ts +++ b/web/src/api/types/mxtypes.ts @@ -74,12 +74,24 @@ export interface Mentions { room: boolean } +export interface RelatesTo { + rel_type?: RelationType + event_id?: EventID + key?: string + + is_falling_back?: boolean + "m.in_reply_to"?: { + event_id?: EventID + } +} + export interface BaseMessageEventContent { msgtype: string body: string formatted_body?: string format?: "org.matrix.custom.html" "m.mentions"?: Mentions + "m.relates_to"?: RelatesTo } export interface TextMessageEventContent extends BaseMessageEventContent { diff --git a/web/src/ui/composer/MessageComposer.tsx b/web/src/ui/composer/MessageComposer.tsx index 8824d88..997e575 100644 --- a/web/src/ui/composer/MessageComposer.tsx +++ b/web/src/ui/composer/MessageComposer.tsx @@ -16,7 +16,7 @@ import React, { use, useCallback, useEffect, useLayoutEffect, useReducer, useRef, useState } from "react" import { ScaleLoader } from "react-spinners" import { RoomStateStore, useRoomEvent } from "@/api/statestore" -import type { EventID, MediaMessageEventContent, Mentions, RoomID } from "@/api/types" +import type { EventID, MediaMessageEventContent, Mentions, RelatesTo, RoomID } from "@/api/types" import useEvent from "@/util/useEvent.ts" import { ClientContext } from "../ClientContext.ts" import { ReplyBody } from "../timeline/ReplyBody.tsx" @@ -90,14 +90,27 @@ const MessageComposer = ({ room, scrollToBottomRef, setReplyToRef }: MessageComp user_ids: [], room: false, } + let relates_to: RelatesTo | undefined = undefined if (replyToEvt) { mentions.user_ids.push(replyToEvt.sender) + relates_to = { + "m.in_reply_to": { + event_id: replyToEvt.event_id, + }, + } + if (replyToEvt.content["m.relates_to"]?.rel_type === "m.thread" + && typeof replyToEvt.content["m.relates_to"]?.event_id === "string") { + relates_to.rel_type = "m.thread" + relates_to.event_id = replyToEvt.content["m.relates_to"].event_id + // TODO set this to true if replying to the last event in a thread? + relates_to.is_falling_back = false + } } client.sendMessage({ room_id: room.roomID, base_content: state.media ?? undefined, text: state.text, - reply_to: replyToEvt?.event_id, + relates_to, mentions, }).catch(err => window.alert("Failed to send message: " + err)) })