Add support for sending Markdown messages

This commit is contained in:
Tulir Asokan 2018-04-18 14:20:57 +03:00
parent 3750d5007f
commit bb36996194
12 changed files with 66 additions and 83 deletions

View File

@ -29,6 +29,7 @@ type MatrixContainer interface {
Start() Start()
Stop() Stop()
SendMessage(roomID, msgtype, message string) (string, error) SendMessage(roomID, msgtype, message string) (string, error)
SendMarkdownMessage(roomID, msgtype, message string) (string, error)
SendTyping(roomID string, typing bool) SendTyping(roomID string, typing bool)
JoinRoom(roomID string) error JoinRoom(roomID string) error
LeaveRoom(roomID string) error LeaveRoom(roomID string) error

View File

@ -89,7 +89,6 @@ type MessageMeta interface {
Timestamp() time.Time Timestamp() time.Time
FormatTime() string FormatTime() string
FormatDate() string FormatDate() string
CopyFrom(from MessageMeta)
} }
// MessageState is an enum to specify if a Message is being sent, failed to send or was successfully sent. // MessageState is an enum to specify if a Message is being sent, failed to send or was successfully sent.

View File

@ -31,6 +31,7 @@ import (
"strings" "strings"
"time" "time"
"gopkg.in/russross/blackfriday.v2"
"maunium.net/go/gomatrix" "maunium.net/go/gomatrix"
"maunium.net/go/gomuks/config" "maunium.net/go/gomuks/config"
"maunium.net/go/gomuks/debug" "maunium.net/go/gomuks/debug"
@ -326,6 +327,30 @@ func (c *Container) SendMessage(roomID, msgtype, text string) (string, error) {
return resp.EventID, nil return resp.EventID, nil
} }
func (c *Container) SendMarkdownMessage(roomID, msgtype, text string) (string, error) {
defer c.gmx.Recover()
html := string(blackfriday.Run([]byte(text)))
if html == text {
return c.SendMessage(roomID, msgtype, text)
}
debug.Print(html)
debug.Print(text)
c.SendTyping(roomID, false)
resp, err := c.client.SendMessageEvent(roomID, "m.room.message",
map[string]interface{}{
"msgtype": msgtype,
"body": text,
"format": "org.matrix.custom.html",
"formatted_body": html,
})
if err != nil {
return "", err
}
return resp.EventID, nil
}
// SendTyping sets whether or not the user is typing in the given room. // SendTyping sets whether or not the user is typing in the given room.
func (c *Container) SendTyping(roomID string, typing bool) { func (c *Container) SendTyping(roomID string, typing bool) {
defer c.gmx.Recover() defer c.gmx.Recover()

View File

@ -162,8 +162,7 @@ func (view *MessageView) AddMessage(ifcMessage ifc.Message, direction ifc.Messag
oldMsg, messageExists := view.messageIDs[message.ID()] oldMsg, messageExists := view.messageIDs[message.ID()]
if messageExists { if messageExists {
oldMsg.CopyFrom(message) view.replaceMessage(oldMsg, message)
message = oldMsg
direction = ifc.IgnoreMessage direction = ifc.IgnoreMessage
} }
@ -181,8 +180,10 @@ func (view *MessageView) AddMessage(ifcMessage ifc.Message, direction ifc.Messag
view.appendBuffer(message) view.appendBuffer(message)
} else if direction == ifc.PrependMessage { } else if direction == ifc.PrependMessage {
view.messages = append([]messages.UIMessage{message}, view.messages...) view.messages = append([]messages.UIMessage{message}, view.messages...)
} else if oldMsg != nil {
view.replaceBuffer(oldMsg, message)
} else { } else {
view.replaceBuffer(message) view.replaceBuffer(message, message)
} }
view.messageIDs[message.ID()] = message view.messageIDs[message.ID()] = message
@ -207,11 +208,20 @@ func (view *MessageView) appendBuffer(message messages.UIMessage) {
view.prevMsgCount++ view.prevMsgCount++
} }
func (view *MessageView) replaceBuffer(message messages.UIMessage) { func (view *MessageView) replaceMessage(original messages.UIMessage, new messages.UIMessage) {
view.messageIDs[new.ID()] = new
for index, msg := range view.messages {
if msg == original {
view.messages[index] = new
}
}
}
func (view *MessageView) replaceBuffer(original messages.UIMessage, new messages.UIMessage) {
start := -1 start := -1
end := -1 end := -1
for index, meta := range view.metaBuffer { for index, meta := range view.metaBuffer {
if meta == message { if meta == original {
if start == -1 { if start == -1 {
start = index start = index
} }
@ -225,13 +235,17 @@ func (view *MessageView) replaceBuffer(message messages.UIMessage) {
end++ end++
} }
view.textBuffer = append(append(view.textBuffer[0:start], message.Buffer()...), view.textBuffer[end:]...) view.textBuffer = append(append(view.textBuffer[0:start], new.Buffer()...), view.textBuffer[end:]...)
if len(message.Buffer()) != end-start+1 { if len(new.Buffer()) != end-start+1 {
metaBuffer := view.metaBuffer[0:start] metaBuffer := view.metaBuffer[0:start]
for range message.Buffer() { for range new.Buffer() {
metaBuffer = append(metaBuffer, message) metaBuffer = append(metaBuffer, new)
} }
view.metaBuffer = append(metaBuffer, view.metaBuffer[end:]...) view.metaBuffer = append(metaBuffer, view.metaBuffer[end:]...)
} else {
for i := start; i < end; i++ {
view.metaBuffer[i] = new
}
} }
} }

View File

@ -61,25 +61,6 @@ func newBaseMessage(id, sender, displayname, msgtype string, timestamp time.Time
func (msg *BaseMessage) RegisterGomuks(gmx ifc.Gomuks) {} func (msg *BaseMessage) RegisterGomuks(gmx ifc.Gomuks) {}
// CopyFrom replaces the content of this message object with the content of the given object.
func (msg *BaseMessage) CopyFrom(from ifc.MessageMeta) {
msg.MsgSender = from.Sender()
msg.MsgSenderColor = from.SenderColor()
fromMsg, ok := from.(UIMessage)
if ok {
msg.MsgSenderID = fromMsg.SenderID()
msg.MsgSender = fromMsg.RealSender()
msg.MsgID = fromMsg.ID()
msg.MsgType = fromMsg.Type()
msg.MsgTimestamp = fromMsg.Timestamp()
msg.MsgState = fromMsg.State()
msg.MsgIsService = fromMsg.IsService()
msg.MsgIsHighlight = fromMsg.IsHighlight()
msg.buffer = nil
}
}
// Sender gets the string that should be displayed as the sender of this message. // Sender gets the string that should be displayed as the sender of this message.
// //
// If the message is being sent, the sender is "Sending...". // If the message is being sent, the sender is "Sending...".

View File

@ -20,7 +20,6 @@ import (
"encoding/gob" "encoding/gob"
"time" "time"
"maunium.net/go/gomuks/interface"
"maunium.net/go/gomuks/ui/messages/tstring" "maunium.net/go/gomuks/ui/messages/tstring"
) )
@ -45,18 +44,6 @@ func (msg *ExpandedTextMessage) GenerateText() tstring.TString {
return msg.MsgText return msg.MsgText
} }
// CopyFrom replaces the content of this message object with the content of the given object.
func (msg *ExpandedTextMessage) CopyFrom(from ifc.MessageMeta) {
msg.BaseTextMessage.CopyFrom(from)
fromExpandedMsg, ok := from.(*ExpandedTextMessage)
if ok {
msg.MsgText = fromExpandedMsg.MsgText
}
msg.RecalculateBuffer()
}
func (msg *ExpandedTextMessage) NotificationContent() string { func (msg *ExpandedTextMessage) NotificationContent() string {
return msg.MsgText.String() return msg.MsgText.String()
} }

View File

@ -85,18 +85,6 @@ func (msg *ImageMessage) Path() string {
return msg.gmx.Matrix().GetCachePath(msg.Homeserver, msg.FileID) return msg.gmx.Matrix().GetCachePath(msg.Homeserver, msg.FileID)
} }
// CopyFrom replaces the content of this message object with the content of the given object.
func (msg *ImageMessage) CopyFrom(from ifc.MessageMeta) {
msg.BaseMessage.CopyFrom(from)
fromImgMsg, ok := from.(*ImageMessage)
if ok {
msg.data = fromImgMsg.data
}
msg.RecalculateBuffer()
}
// CalculateBuffer generates the internal buffer for this message that consists // CalculateBuffer generates the internal buffer for this message that consists
// of the text of this message split into lines at most as wide as the width // of the text of this message split into lines at most as wide as the width
// parameter. // parameter.

View File

@ -20,7 +20,6 @@ import (
"time" "time"
"maunium.net/go/tcell" "maunium.net/go/tcell"
"maunium.net/go/gomuks/interface"
) )
// BasicMeta is a simple variable store implementation of MessageMeta. // BasicMeta is a simple variable store implementation of MessageMeta.
@ -66,12 +65,3 @@ func (meta *BasicMeta) TextColor() tcell.Color {
func (meta *BasicMeta) TimestampColor() tcell.Color { func (meta *BasicMeta) TimestampColor() tcell.Color {
return meta.BTimestampColor return meta.BTimestampColor
} }
// CopyFrom replaces the content of this meta object with the content of the given object.
func (meta *BasicMeta) CopyFrom(from ifc.MessageMeta) {
meta.BSender = from.Sender()
meta.BTimestamp = from.Timestamp()
meta.BSenderColor = from.SenderColor()
meta.BTextColor = from.TextColor()
meta.BTimestampColor = from.TimestampColor()
}

View File

@ -37,6 +37,9 @@ var matrixToURL = regexp.MustCompile("^(?:https?://)?(?:www\\.)?matrix\\.to/#/([
type MatrixHTMLProcessor struct { type MatrixHTMLProcessor struct {
text tstring.TString text tstring.TString
sender string
msgtype string
indent string indent string
listType string listType string
lineIsNew bool lineIsNew bool
@ -52,7 +55,11 @@ func (parser *MatrixHTMLProcessor) newline() {
} }
} }
func (parser *MatrixHTMLProcessor) Preprocess() {} func (parser *MatrixHTMLProcessor) Preprocess() {
if parser.msgtype == "m.emote" {
parser.text = tstring.NewColorTString(fmt.Sprintf("* %s ", parser.sender), widget.GetHashColor(parser.sender))
}
}
func (parser *MatrixHTMLProcessor) HandleText(text string) { func (parser *MatrixHTMLProcessor) HandleText(text string) {
style := tcell.StyleDefault style := tcell.StyleDefault
@ -170,12 +177,15 @@ func (parser *MatrixHTMLProcessor) Postprocess() {
} }
// ParseHTMLMessage parses a HTML-formatted Matrix event into a UIMessage. // ParseHTMLMessage parses a HTML-formatted Matrix event into a UIMessage.
func ParseHTMLMessage(room *rooms.Room, evt *gomatrix.Event) tstring.TString { func ParseHTMLMessage(room *rooms.Room, evt *gomatrix.Event, senderDisplayname string) tstring.TString {
htmlData, _ := evt.Content["formatted_body"].(string) htmlData, _ := evt.Content["formatted_body"].(string)
msgtype, _ := evt.Content["msgtype"].(string)
processor := &MatrixHTMLProcessor{ processor := &MatrixHTMLProcessor{
room: room, room: room,
text: tstring.NewBlankTString(), text: tstring.NewBlankTString(),
msgtype: msgtype,
sender: senderDisplayname,
indent: "", indent: "",
listType: "", listType: "",
lineIsNew: true, lineIsNew: true,

View File

@ -60,7 +60,7 @@ func ParseMessage(gmx ifc.Gomuks, room *rooms.Room, evt *gomatrix.Event) message
case "m.text", "m.notice", "m.emote": case "m.text", "m.notice", "m.emote":
format, hasFormat := evt.Content["format"].(string) format, hasFormat := evt.Content["format"].(string)
if hasFormat && format == "org.matrix.custom.html" { if hasFormat && format == "org.matrix.custom.html" {
text := ParseHTMLMessage(room, evt) text := ParseHTMLMessage(room, evt, displayname)
return messages.NewExpandedTextMessage(evt.ID, evt.Sender, displayname, msgtype, text, ts) return messages.NewExpandedTextMessage(evt.ID, evt.Sender, displayname, msgtype, text, ts)
} else { } else {
text, _ := evt.Content["body"].(string) text, _ := evt.Content["body"].(string)

View File

@ -56,18 +56,6 @@ func (msg *TextMessage) getCache() tstring.TString {
return msg.cache return msg.cache
} }
// CopyFrom replaces the content of this message object with the content of the given object.
func (msg *TextMessage) CopyFrom(from ifc.MessageMeta) {
msg.BaseTextMessage.CopyFrom(from)
fromTextMsg, ok := from.(*TextMessage)
if ok {
msg.MsgText = fromTextMsg.MsgText
}
msg.cache = nil
msg.RecalculateBuffer()
}
func (msg *TextMessage) SetType(msgtype string) { func (msg *TextMessage) SetType(msgtype string) {
msg.BaseTextMessage.SetType(msgtype) msg.BaseTextMessage.SetType(msgtype)
msg.cache = nil msg.cache = nil

View File

@ -144,7 +144,7 @@ func (view *MainView) SendMessage(roomView *RoomView, text string) {
func (view *MainView) sendTempMessage(roomView *RoomView, tempMessage ifc.Message, text string) { func (view *MainView) sendTempMessage(roomView *RoomView, tempMessage ifc.Message, text string) {
defer view.gmx.Recover() defer view.gmx.Recover()
eventID, err := view.matrix.SendMessage(roomView.Room.ID, tempMessage.Type(), text) eventID, err := view.matrix.SendMarkdownMessage(roomView.Room.ID, tempMessage.Type(), text)
if err != nil { if err != nil {
tempMessage.SetState(ifc.MessageStateFailed) tempMessage.SetState(ifc.MessageStateFailed)
roomView.SetStatus(fmt.Sprintf("Failed to send message: %s", err)) roomView.SetStatus(fmt.Sprintf("Failed to send message: %s", err))