Add support for replying and reacting to messages

This commit is contained in:
Tulir Asokan
2020-03-01 00:33:37 +02:00
parent 699d0ea4e0
commit 9d132d328b
10 changed files with 160 additions and 19 deletions

View File

@ -87,6 +87,11 @@ func NewCommandProcessor(parent *MainView) *CommandProcessor {
"createroom": {"create"},
"dm": {"pm"},
"query": {"pm"},
"r": {"reply"},
"delete": {"redact"},
"remove": {"redact"},
"rm": {"redact"},
"del": {"redact"},
},
commands: map[string]CommandHandler{
"unknown-command": cmdUnknownCommand,
@ -107,6 +112,9 @@ func NewCommandProcessor(parent *MainView) *CommandProcessor {
"logout": cmdLogout,
"accept": cmdAccept,
"reject": cmdReject,
"reply": cmdReply,
"redact": cmdRedact,
"react": cmdReact,
"sendevent": cmdSendEvent,
"msendevent": cmdMSendEvent,
"setstate": cmdSetState,

View File

@ -41,7 +41,6 @@ import (
func cmdMe(cmd *Command) {
text := strings.Join(cmd.Args, " ")
go cmd.Room.SendMessage(mautrix.MsgEmote, text)
cmd.UI.Render()
}
// GradientTable from https://github.com/lucasb-eyer/go-colorful/blob/master/doc/gradientgen/gradientgen.go
@ -87,10 +86,9 @@ func makeRainbow(cmd *Command, msgtype mautrix.MessageType) {
continue
}
color := rainbow.GetInterpolatedColorFor(float64(i) / float64(len(text))).Hex()
_, _ = fmt.Fprintf(&html, "<font color=\"%s\">%c</font>", color, char)
_, _ = fmt.Fprintf(&html, "<font data-mx-color=\"%[1]s\" color=\"%[1]s\">%[2]c</font>", color, char)
}
go cmd.Room.SendMessage(msgtype, html.String())
cmd.UI.Render()
}
func cmdRainbow(cmd *Command) {
@ -103,7 +101,6 @@ func cmdRainbowMe(cmd *Command) {
func cmdNotice(cmd *Command) {
go cmd.Room.SendMessage(mautrix.MsgNotice, strings.Join(cmd.Args, " "))
cmd.UI.Render()
}
func cmdAccept(cmd *Command) {
@ -141,6 +138,42 @@ func cmdID(cmd *Command) {
cmd.Reply("The internal ID of this room is %s", cmd.Room.MxRoom().ID)
}
type SelectReason string
const (
SelectReply SelectReason = "reply to"
SelectReact = "react to"
SelectRedact = "redact"
)
func cmdReply(cmd *Command) {
cmd.Room.selecting = true
cmd.Room.selectReason = SelectReply
cmd.Room.selectContent = strings.Join(cmd.Args, " ")
cmd.Room.OnSelect(cmd.Room.MessageView().selected)
}
func cmdRedact(cmd *Command) {
cmd.Reply("Not yet implemented 3:")
// This needs to be implemented in RoomView's OnSelect method
//cmd.Room.selecting = true
//cmd.Room.selectReason = SelectRedact
//cmd.Room.OnSelect(cmd.Room.MessageView().selected)
}
func cmdReact(cmd *Command) {
if len(cmd.Args) == 0 {
cmd.Reply("Usage: /react <reaction>")
return
}
cmd.Room.selecting = true
cmd.Room.selectReason = SelectReact
cmd.Room.selectContent = strings.Join(cmd.Args, " ")
cmd.Room.OnSelect(cmd.Room.MessageView().selected)
}
func cmdTags(cmd *Command) {
tags := cmd.Room.MxRoom().RawTags
if len(cmd.Args) > 0 && cmd.Args[0] == "--internal" {
@ -298,6 +331,9 @@ Things: rooms, users, baremessages, images, typingnotif
/notice <message> - Send a notice (generally used for bot messages).
/rainbow <message> - Send rainbow text (markdown not supported).
/rainbowme <message> - Send rainbow text in an emote.
/reply [text] - Reply to the selected message.
/react <reaction> - React to the selected message.
/redact - Redact the selected message.
# Rooms
/pm <user id> <...> - Create a private chat with the given user(s).

View File

@ -338,7 +338,11 @@ func (view *MessageView) SetSelected(message *messages.UIMessage) {
if view.selected != nil {
view.selected.IsSelected = false
}
view.selected = message
if message != nil && (view.selected == message || message.IsService) {
view.selected = nil
} else {
view.selected = message
}
if view.selected != nil {
view.selected.IsSelected = true
}
@ -349,11 +353,9 @@ func (view *MessageView) handleMessageClick(message *messages.UIMessage, mod tce
open.Open(msg.Path())
// No need to re-render
return false
} else if message.IsService {
// Can't select service messages
return false
}
view.SetSelected(message)
view.parent.OnSelect(view.selected)
return true
}

View File

@ -129,7 +129,6 @@ func ParseMessage(matrix ifc.MatrixContainer, room *rooms.Room, evt *event.Event
evt.Content.RemoveReplyFallback()
}
if len(evt.Gomuks.Edits) > 0 {
evt = evt.SomewhatDangerousCopy()
evt.Content = *evt.Gomuks.Edits[len(evt.Gomuks.Edits)-1].Content.NewContent
}
switch evt.Content.MsgType {

View File

@ -67,6 +67,12 @@ type RoomView struct {
typing []string
selecting bool
selectReason SelectReason
selectContent string
replying *event.Event
editing *event.Event
editMoveText string
@ -148,14 +154,43 @@ func (view *RoomView) Focus() {
}
func (view *RoomView) Blur() {
view.MessageView().SetSelected(nil)
view.input.Blur()
}
func (view *RoomView) OnSelect(message *messages.UIMessage) {
if !view.selecting || message == nil {
return
}
switch view.selectReason {
case SelectReply:
view.replying = message.Event
if len(view.selectContent) > 0 {
go view.SendMessage(mautrix.MsgText, view.selectContent)
}
case SelectReact:
go view.SendReaction(message.EventID, view.selectContent)
case SelectRedact:
// TODO redact
}
view.selecting = false
view.selectContent = ""
view.MessageView().SetSelected(nil)
}
func (view *RoomView) GetStatus() string {
var buf strings.Builder
if view.editing != nil {
buf.WriteString("Editing message - ")
} else if view.replying != nil {
buf.WriteString("Replying to ")
buf.WriteString(view.replying.Sender)
buf.WriteString(" - ")
} else if view.selecting {
buf.WriteString("Selecting message to")
buf.WriteString(string(view.selectReason))
buf.WriteString(" - ")
}
if len(view.completions.list) > 0 {
@ -373,6 +408,8 @@ func (view *RoomView) SetEditing(evt *event.Event) {
view.editMoveText = view.GetInputText()
}
view.editing = evt
// replying should never be non-nil when SetEditing, but do this just to be safe
view.replying = nil
text := view.editing.Content.Body
if view.editing.Content.MsgType == mautrix.MsgEmote {
text = "/me " + text
@ -393,7 +430,9 @@ func (view *RoomView) findMessageToEdit(forward bool) *event.Event {
index = len(msgs) - i - 1
}
evt := msgs[index]
if currentFound {
if evt.EventID == "" || evt.EventID == evt.TxnID {
continue
} else if currentFound {
if evt.SenderID == self && evt.Event.Type == mautrix.EventMessage {
return evt.Event
}
@ -413,6 +452,9 @@ func (view *RoomView) EditNext() {
}
func (view *RoomView) EditPrevious() {
if view.replying != nil {
return
}
foundEvent := view.findMessageToEdit(false)
if foundEvent != nil {
view.SetEditing(foundEvent)
@ -468,6 +510,35 @@ func (view *RoomView) InputSubmit(text string) {
}
view.editMoveText = ""
view.SetInputText("")
view.MessageView().SetSelected(nil)
}
func (view *RoomView) SendReaction(eventID string, reaction string) {
defer debug.Recover()
debug.Print("Reacting to", eventID, "in", view.Room.ID, "with", reaction)
eventID, err := view.parent.matrix.SendEvent(&event.Event{
Event: &mautrix.Event{
Type: mautrix.EventReaction,
RoomID: view.Room.ID,
Content: mautrix.Content{
RelatesTo: &mautrix.RelatesTo{
Type: mautrix.RelAnnotation,
EventID: eventID,
Key: reaction,
},
},
},
})
if err != nil {
if httpErr, ok := err.(mautrix.HTTPError); ok {
err = httpErr
if respErr := httpErr.RespError; respErr != nil {
err = respErr
}
}
view.AddServiceMessage(fmt.Sprintf("Failed to send reaction: %v", err))
view.parent.parent.Render()
}
}
func (view *RoomView) SendMessage(msgtype mautrix.MessageType, text string) {
@ -476,10 +547,23 @@ func (view *RoomView) SendMessage(msgtype mautrix.MessageType, text string) {
if !view.config.Preferences.DisableEmojis {
text = emoji.Sprint(text)
}
evt := view.parent.matrix.PrepareMarkdownMessage(view.Room.ID, msgtype, text, view.editing)
msg := view.parseEvent(evt)
var rel *ifc.Relation
if view.editing != nil {
rel = &ifc.Relation{
Type: mautrix.RelReplace,
Event: view.editing,
}
} else if view.replying != nil {
rel = &ifc.Relation{
Type: mautrix.RelReference,
Event: view.replying,
}
}
evt := view.parent.matrix.PrepareMarkdownMessage(view.Room.ID, msgtype, text, rel)
msg := view.parseEvent(evt.SomewhatDangerousCopy())
view.content.AddMessage(msg, AppendMessage)
view.editing = nil
view.replying = nil
view.status.SetText(view.GetStatus())
eventID, err := view.parent.matrix.SendEvent(evt)
if err != nil {