web/composer: send thread message when replying in thread

This commit is contained in:
Tulir Asokan 2024-10-22 19:53:10 +03:00
parent 014c8c07a8
commit c4266fbc22
5 changed files with 49 additions and 8 deletions

View file

@ -41,7 +41,7 @@ func (h *HiClient) handleJSONCommand(ctx context.Context, req *JSONCommand) (any
}) })
case "send_message": case "send_message":
return unmarshalAndCall(req.Data, func(params *sendMessageParams) (*database.Event, error) { 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": case "send_event":
return unmarshalAndCall(req.Data, func(params *sendEventParams) (*database.Event, error) { return unmarshalAndCall(req.Data, func(params *sendEventParams) (*database.Event, error) {
@ -122,7 +122,7 @@ type sendMessageParams struct {
RoomID id.RoomID `json:"room_id"` RoomID id.RoomID `json:"room_id"`
BaseContent *event.MessageEventContent `json:"base_content"` BaseContent *event.MessageEventContent `json:"base_content"`
Text string `json:"text"` Text string `json:"text"`
ReplyTo id.EventID `json:"reply_to"` RelatesTo *event.RelatesTo `json:"relates_to"`
Mentions *event.Mentions `json:"mentions"` Mentions *event.Mentions `json:"mentions"`
} }

View file

@ -32,7 +32,14 @@ var (
rainbowWithHTML = goldmark.New(format.Extensions, format.HTMLOptions, goldmark.WithExtensions(rainbow.Extension)) 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 var content event.MessageEventContent
if strings.HasPrefix(text, "/rainbow ") { if strings.HasPrefix(text, "/rainbow ") {
text = strings.TrimPrefix(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 != "" { if relatesTo != nil {
content.RelatesTo = (&event.RelatesTo{}).SetReplyTo(replyTo) 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) return h.Send(ctx, roomID, event.EventMessage, &content)
} }

View file

@ -27,6 +27,7 @@ import type {
RPCEvent, RPCEvent,
RawDBEvent, RawDBEvent,
ReceiptType, ReceiptType,
RelatesTo,
ResolveAliasResponse, ResolveAliasResponse,
RoomAlias, RoomAlias,
RoomID, RoomID,
@ -50,7 +51,7 @@ export interface SendMessageParams {
base_content?: MessageEventContent base_content?: MessageEventContent
text: string text: string
media_path?: string media_path?: string
reply_to?: EventID relates_to?: RelatesTo
mentions?: Mentions mentions?: Mentions
} }

View file

@ -74,12 +74,24 @@ export interface Mentions {
room: boolean 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 { export interface BaseMessageEventContent {
msgtype: string msgtype: string
body: string body: string
formatted_body?: string formatted_body?: string
format?: "org.matrix.custom.html" format?: "org.matrix.custom.html"
"m.mentions"?: Mentions "m.mentions"?: Mentions
"m.relates_to"?: RelatesTo
} }
export interface TextMessageEventContent extends BaseMessageEventContent { export interface TextMessageEventContent extends BaseMessageEventContent {

View file

@ -16,7 +16,7 @@
import React, { use, useCallback, useEffect, useLayoutEffect, useReducer, useRef, useState } from "react" import React, { use, useCallback, useEffect, useLayoutEffect, useReducer, useRef, useState } from "react"
import { ScaleLoader } from "react-spinners" import { ScaleLoader } from "react-spinners"
import { RoomStateStore, useRoomEvent } from "@/api/statestore" 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 useEvent from "@/util/useEvent.ts"
import { ClientContext } from "../ClientContext.ts" import { ClientContext } from "../ClientContext.ts"
import { ReplyBody } from "../timeline/ReplyBody.tsx" import { ReplyBody } from "../timeline/ReplyBody.tsx"
@ -90,14 +90,27 @@ const MessageComposer = ({ room, scrollToBottomRef, setReplyToRef }: MessageComp
user_ids: [], user_ids: [],
room: false, room: false,
} }
let relates_to: RelatesTo | undefined = undefined
if (replyToEvt) { if (replyToEvt) {
mentions.user_ids.push(replyToEvt.sender) 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({ client.sendMessage({
room_id: room.roomID, room_id: room.roomID,
base_content: state.media ?? undefined, base_content: state.media ?? undefined,
text: state.text, text: state.text,
reply_to: replyToEvt?.event_id, relates_to,
mentions, mentions,
}).catch(err => window.alert("Failed to send message: " + err)) }).catch(err => window.alert("Failed to send message: " + err))
}) })