Start refactoring various things in message rendering and sending

This commit is contained in:
Tulir Asokan 2019-04-09 18:45:41 +03:00
parent 2b6c435e50
commit dbee49476d
13 changed files with 178 additions and 320 deletions

View File

@ -19,11 +19,9 @@ package ifc
import (
"time"
"maunium.net/go/mautrix"
"maunium.net/go/tcell"
"maunium.net/go/gomuks/matrix/pushrules"
"maunium.net/go/gomuks/matrix/rooms"
"maunium.net/go/mautrix"
)
type UIProvider func(gmx Gomuks) GomuksUI
@ -50,7 +48,6 @@ type MainView interface {
UpdateTags(room *rooms.Room)
SetTyping(roomID string, users []string)
ParseEvent(roomView RoomView, evt *mautrix.Event) Message
NotifyMessage(room *rooms.Room, message Message, should pushrules.PushActionArrayShould)
InitialSyncDone()
@ -71,50 +68,17 @@ type RoomView interface {
SetTyping(users []string)
UpdateUserList()
NewTempMessage(msgtype mautrix.MessageType, text string) Message
AddMessage(message Message, direction MessageDirection)
ParseEvent(evt *mautrix.Event) Message
AppendMessage(message Message)
MarkMessageFailed(message Message)
AddServiceMessage(message string)
}
type MessageMeta interface {
Sender() string
SenderColor() tcell.Color
TextColor() tcell.Color
TimestampColor() tcell.Color
Timestamp() time.Time
FormatTime() string
FormatDate() string
}
// MessageState is an enum to specify if a Message is being sent, failed to send or was successfully sent.
type MessageState int
// Allowed MessageStates.
const (
MessageStateSending MessageState = iota
MessageStateDefault
MessageStateFailed
)
type Message interface {
MessageMeta
SetIsHighlight(isHighlight bool)
IsHighlight() bool
SetIsService(isService bool)
IsService() bool
SetID(id string)
ID() string
SetType(msgtype mautrix.MessageType)
Type() mautrix.MessageType
NotificationContent() string
SetState(state MessageState)
State() MessageState
TxnID() string
SenderID() string
Timestamp() time.Time
NotificationSenderName() string
NotificationContent() string
}

View File

@ -303,6 +303,7 @@ func (c *Container) HandleMessage(source EventSource, evt *mautrix.Event) {
debug.Printf("Failed to add event %s to history: %v", evt.ID, err)
}
// TODO switch to roomView.AddEvent
message := mainView.ParseEvent(roomView, evt)
if message != nil {
roomView.AddMessage(message, ifc.AppendMessage)
@ -494,10 +495,7 @@ func (c *Container) SendMessage(roomID string, msgtype mautrix.MessageType, text
var mentionRegex = regexp.MustCompile("\\[(.+?)]\\(https://matrix.to/#/@.+?:.+?\\)")
var roomRegex = regexp.MustCompile("\\[.+?]\\(https://matrix.to/#/(#.+?:[^/]+?)\\)")
// SendMarkdownMessage sends a message with the given text to the given room.
//
// If the given text contains markdown formatting symbols, it will be rendered into HTML before sending.
// Otherwise, it will be sent as plain text.
// SendMarkdownMessage sends a message with the given markdown text to the given room.
func (c *Container) SendMarkdownMessage(roomID string, msgtype mautrix.MessageType, text string) (string, error) {
defer debug.Recover()
@ -509,8 +507,23 @@ func (c *Container) SendMarkdownMessage(roomID string, msgtype mautrix.MessageTy
content.Body = roomRegex.ReplaceAllString(content.Body, "$1")
c.SendTyping(roomID, false)
resp, err := c.client.SendMessageEvent(roomID, mautrix.EventMessage, content)
roomView := c.ui.MainView().GetRoom(roomID)
txnID := c.client.TxnID()
localEcho := roomView.ParseEvent(&mautrix.Event{
Sender: c.config.UserID,
Type: mautrix.EventMessage,
Timestamp: time.Now().UnixNano() / 1e6,
RoomID: roomID,
Content: content,
Unsigned: mautrix.Unsigned{
TransactionID: txnID,
OutgoingState: mautrix.EventStateLocalEcho,
},
})
roomView.AppendMessage(localEcho)
resp, err := c.client.SendMessageEvent(roomID, mautrix.EventMessage, content, mautrix.ReqSendEvent{TransactionID: txnID})
if err != nil {
roomView.MarkMessageFailed(localEcho, err)
return "", err
}
return resp.EventID, nil

View File

@ -31,7 +31,6 @@ import (
"maunium.net/go/gomuks/interface"
"maunium.net/go/gomuks/lib/open"
"maunium.net/go/gomuks/ui/messages"
"maunium.net/go/gomuks/ui/messages/tstring"
"maunium.net/go/gomuks/ui/widget"
)
@ -57,8 +56,7 @@ type MessageView struct {
messageIDs map[string]messages.UIMessage
messages []messages.UIMessage
textBuffer []tstring.TString
metaBuffer []ifc.MessageMeta
msgBuffer []messages.UIMessage
}
func NewMessageView(parent *RoomView) *MessageView {
@ -72,8 +70,7 @@ func NewMessageView(parent *RoomView) *MessageView {
messages: make([]messages.UIMessage, 0),
messageIDs: make(map[string]messages.UIMessage),
textBuffer: make([]tstring.TString, 0),
metaBuffer: make([]ifc.MessageMeta, 0),
msgBuffer: make([]messages.UIMessage, 0),
width: 80,
widestSender: 5,
@ -115,10 +112,16 @@ func (view *MessageView) AddMessage(ifcMessage ifc.Message, direction ifc.Messag
return
}
oldMsg, messageExists := view.messageIDs[message.ID()]
if messageExists {
var oldMsg messages.UIMessage
var messageExists bool
if oldMsg, messageExists = view.messageIDs[message.ID()]; messageExists {
view.replaceMessage(oldMsg, message)
direction = ifc.IgnoreMessage
} else if oldMsg, messageExists = view.messageIDs[message.TxnID()]; messageExists {
view.replaceMessage(oldMsg, message)
delete(view.messageIDs, message.TxnID())
direction = ifc.IgnoreMessage
}
view.updateWidestSender(message.Sender())
@ -149,20 +152,19 @@ func (view *MessageView) AddMessage(ifcMessage ifc.Message, direction ifc.Messag
}
func (view *MessageView) appendBuffer(message messages.UIMessage) {
if len(view.metaBuffer) > 0 {
prevMeta := view.metaBuffer[len(view.metaBuffer)-1]
if len(view.msgBuffer) > 0 {
prevMeta := view.msgBuffer[len(view.msgBuffer)-1]
if prevMeta != nil && prevMeta.FormatDate() != message.FormatDate() {
view.textBuffer = append(view.textBuffer, tstring.NewColorTString(
/* FIXME view.textBuffer = append(view.textBuffer, tstring.NewColorTString(
fmt.Sprintf("Date changed to %s", message.FormatDate()),
tcell.ColorGreen))
view.metaBuffer = append(view.metaBuffer, &messages.BasicMeta{
BTimestampColor: tcell.ColorDefault, BTextColor: tcell.ColorGreen})
view.msgBuffer = append(view.msgBuffer, &messages.BasicMeta{
BTimestampColor: tcell.ColorDefault, BTextColor: tcell.ColorGreen})*/
}
}
for i := 0; i < message.Height(); i++ {
view.textBuffer = append(view.textBuffer, nil)
view.metaBuffer = append(view.metaBuffer, message)
view.msgBuffer = append(view.msgBuffer, message)
}
view.prevMsgCount++
}
@ -179,7 +181,7 @@ func (view *MessageView) replaceMessage(original messages.UIMessage, new message
func (view *MessageView) replaceBuffer(original messages.UIMessage, new messages.UIMessage) {
start := -1
end := -1
for index, meta := range view.metaBuffer {
for index, meta := range view.msgBuffer {
if meta == original {
if start == -1 {
start = index
@ -197,7 +199,7 @@ func (view *MessageView) replaceBuffer(original messages.UIMessage, new messages
return
}
if len(view.textBuffer) > end {
if len(view.msgBuffer) > end {
end++
}
@ -205,17 +207,15 @@ func (view *MessageView) replaceBuffer(original messages.UIMessage, new messages
new.CalculateBuffer(view.prevPrefs, view.prevWidth)
}
textBuf := make([]tstring.TString, new.Height())
view.textBuffer = append(append(view.textBuffer[0:start], textBuf...), view.textBuffer[end:]...)
if new.Height() != end-start {
metaBuffer := view.metaBuffer[0:start]
metaBuffer := view.msgBuffer[0:start]
for i := 0; i < new.Height(); i++ {
metaBuffer = append(metaBuffer, new)
}
view.metaBuffer = append(metaBuffer, view.metaBuffer[end:]...)
view.msgBuffer = append(metaBuffer, view.msgBuffer[end:]...)
} else {
for i := start; i < end; i++ {
view.metaBuffer[i] = new
view.msgBuffer[i] = new
}
}
}
@ -230,8 +230,7 @@ func (view *MessageView) recalculateBuffers() {
if !prefs.BareMessageView {
width -= view.TimestampWidth + TimestampSenderGap + view.widestSender + SenderMessageGap
}
view.textBuffer = []tstring.TString{}
view.metaBuffer = []ifc.MessageMeta{}
view.msgBuffer = []messages.UIMessage{}
view.prevMsgCount = 0
for i, message := range view.messages {
if message == nil {
@ -249,7 +248,7 @@ func (view *MessageView) recalculateBuffers() {
view.prevPrefs = prefs
}
func (view *MessageView) handleMessageClick(message ifc.MessageMeta) bool {
func (view *MessageView) handleMessageClick(message messages.UIMessage) bool {
switch message := message.(type) {
case *messages.ImageMessage:
open.Open(message.Path())
@ -259,21 +258,15 @@ func (view *MessageView) handleMessageClick(message ifc.MessageMeta) bool {
return false
}
func (view *MessageView) handleUsernameClick(message ifc.MessageMeta, prevMessage ifc.MessageMeta) bool {
uiMessage, ok := message.(messages.UIMessage)
if !ok {
func (view *MessageView) handleUsernameClick(message messages.UIMessage, prevMessage messages.UIMessage) bool {
if prevMessage != nil && prevMessage.Sender() == message.Sender() {
return false
}
prevUIMessage, _ := prevMessage.(messages.UIMessage)
if prevUIMessage != nil && prevUIMessage.Sender() == uiMessage.Sender() {
if len(message.Sender()) == 0 {
return false
}
if len(uiMessage.Sender()) == 0 {
return false
}
sender := fmt.Sprintf("[%s](https://matrix.to/#/%s)", uiMessage.Sender(), uiMessage.SenderID())
sender := fmt.Sprintf("[%s](https://matrix.to/#/%s)", message.Sender(), message.SenderID())
cursorPos := view.parent.input.GetCursorOffset()
text := view.parent.input.GetText()
@ -317,10 +310,10 @@ func (view *MessageView) OnMouseEvent(event mauview.MouseEvent) bool {
return false
}
message := view.metaBuffer[line]
var prevMessage ifc.MessageMeta
message := view.msgBuffer[line]
var prevMessage messages.UIMessage
if y != 0 && line > 0 {
prevMessage = view.metaBuffer[line-1]
prevMessage = view.msgBuffer[line-1]
}
usernameX := view.TimestampWidth + TimestampSenderGap
@ -358,12 +351,11 @@ func (view *MessageView) Height() int {
}
func (view *MessageView) TotalHeight() int {
return len(view.textBuffer)
return len(view.msgBuffer)
}
func (view *MessageView) IsAtTop() bool {
totalHeight := len(view.textBuffer)
return view.ScrollOffset >= totalHeight-view.height+PaddingAtTop
return view.ScrollOffset >= len(view.msgBuffer)-view.height+PaddingAtTop
}
const (
@ -429,7 +421,7 @@ func (view *MessageView) CapturePlaintext(height int) string {
continue
}
meta := view.metaBuffer[index]
meta := view.msgBuffer[index]
message, ok := meta.(messages.UIMessage)
if ok && message != prevMessage {
var sender string
@ -456,7 +448,6 @@ func (view *MessageView) Draw(screen mauview.Screen) {
usernameX := view.TimestampWidth + TimestampSenderGap
messageX := usernameX + view.widestSender + SenderMessageGap
separatorX := usernameX + view.widestSender + SenderSeparatorGap
bareMode := view.config.Preferences.BareMessageView
if bareMode {
@ -465,20 +456,15 @@ func (view *MessageView) Draw(screen mauview.Screen) {
indexOffset := view.getIndexOffset(screen, view.height, messageX)
if len(view.textBuffer) != len(view.metaBuffer) {
debug.Printf("Unexpected text/meta buffer length mismatch: %d != %d.", len(view.textBuffer), len(view.metaBuffer))
view.prevMsgCount = 0
return
}
scrollBarHeight, scrollBarPos := view.calculateScrollBar(view.height)
var prevMeta ifc.MessageMeta
viewStart := 0
if indexOffset < 0 {
viewStart = -indexOffset
}
if !bareMode {
separatorX := usernameX + view.widestSender + SenderSeparatorGap
scrollBarHeight, scrollBarPos := view.calculateScrollBar(view.height)
for line := viewStart; line < view.height; line++ {
showScrollbar := line-viewStart >= scrollBarPos-scrollBarHeight && line-viewStart < scrollBarPos
isTop := line == viewStart && view.ScrollOffset+view.height >= view.TotalHeight()
@ -486,38 +472,33 @@ func (view *MessageView) Draw(screen mauview.Screen) {
borderChar, borderStyle := getScrollbarStyle(showScrollbar, isTop, isBottom)
if !bareMode {
screen.SetContent(separatorX, line, borderChar, nil, borderStyle)
}
}
var prevMsg messages.UIMessage
for line := viewStart; line < view.height && indexOffset+line < view.TotalHeight(); line++ {
index := indexOffset + line
text, meta := view.textBuffer[index], view.metaBuffer[index]
if meta != prevMeta {
if len(meta.FormatTime()) > 0 {
widget.WriteLineSimpleColor(screen, meta.FormatTime(), 0, line, meta.TimestampColor())
msg := view.msgBuffer[index]
if msg != prevMsg {
if len(msg.FormatTime()) > 0 {
widget.WriteLineSimpleColor(screen, msg.FormatTime(), 0, line, msg.TimestampColor())
}
// TODO hiding senders might not be that nice after all, maybe an option? (disabled for now)
//if !bareMode && (prevMeta == nil || meta.Sender() != prevMeta.Sender()) {
//if !bareMode && (prevMsg == nil || meta.Sender() != prevMsg.Sender()) {
widget.WriteLineColor(
screen, mauview.AlignRight, meta.Sender(),
screen, mauview.AlignRight, msg.Sender(),
usernameX, line, view.widestSender,
meta.SenderColor())
msg.SenderColor())
//}
prevMeta = meta
prevMsg = msg
}
message, ok := meta.(messages.UIMessage)
if ok {
for i := index - 1; i >= 0 && view.metaBuffer[i] == meta; i-- {
for i := index - 1; i >= 0 && view.msgBuffer[i] == msg; i-- {
line--
}
message.Draw(mauview.NewProxyScreen(screen, messageX, line, view.width-messageX, message.Height()))
line += message.Height() - 1
} else {
text.Draw(screen, messageX, line)
}
msg.Draw(mauview.NewProxyScreen(screen, messageX, line, view.width-messageX, msg.Height()))
line += msg.Height() - 1
}
}

View File

@ -17,7 +17,7 @@
package messages
import (
"encoding/gob"
"encoding/json"
"time"
"maunium.net/go/mautrix"
@ -29,38 +29,51 @@ import (
"maunium.net/go/gomuks/ui/widget"
)
func init() {
gob.Register(&BaseMessage{})
}
type BaseMessage struct {
MsgID string
MsgTxnID string
MsgType mautrix.MessageType
MsgSenderID string
MsgSender string
MsgSenderColor tcell.Color
MsgTimestamp time.Time
MsgState ifc.MessageState
MsgState mautrix.OutgoingEventState
MsgIsHighlight bool
MsgIsService bool
MsgSource json.RawMessage
buffer []tstring.TString
plainBuffer []tstring.TString
}
func newBaseMessage(id, sender, displayname string, msgtype mautrix.MessageType, timestamp time.Time) BaseMessage {
func newBaseMessage(event *mautrix.Event, displayname string) BaseMessage {
msgtype := event.Content.MsgType
if len(msgtype) == 0 {
msgtype = mautrix.MessageType(event.Type.String())
}
return BaseMessage{
MsgSenderID: sender,
MsgSenderID: event.Sender,
MsgSender: displayname,
MsgTimestamp: timestamp,
MsgSenderColor: widget.GetHashColor(sender),
MsgTimestamp: unixToTime(event.Timestamp),
MsgSenderColor: widget.GetHashColor(event.Sender),
MsgType: msgtype,
MsgID: id,
MsgState: ifc.MessageStateDefault,
MsgID: event.ID,
MsgTxnID: event.Unsigned.TransactionID,
MsgState: event.Unsigned.OutgoingState,
MsgIsHighlight: false,
MsgIsService: false,
MsgSource: event.Content.VeryRaw,
}
}
func unixToTime(unix int64) time.Time {
timestamp := time.Now()
if unix != 0 {
timestamp = time.Unix(unix/1000, unix%1000*1000)
}
return timestamp
}
func (msg *BaseMessage) RegisterMatrix(matrix ifc.MatrixContainer) {}
// Sender gets the string that should be displayed as the sender of this message.
@ -71,9 +84,9 @@ func (msg *BaseMessage) RegisterMatrix(matrix ifc.MatrixContainer) {}
// In any other case, the sender is the display name of the user who sent the message.
func (msg *BaseMessage) Sender() string {
switch msg.MsgState {
case ifc.MessageStateSending:
case mautrix.EventStateLocalEcho:
return "Sending..."
case ifc.MessageStateFailed:
case mautrix.EventStateSendFail:
return "Error"
}
switch msg.MsgType {
@ -93,13 +106,17 @@ func (msg *BaseMessage) RealSender() string {
return msg.MsgSender
}
func (msg *BaseMessage) NotificationSenderName() string {
return msg.MsgSender
}
func (msg *BaseMessage) getStateSpecificColor() tcell.Color {
switch msg.MsgState {
case ifc.MessageStateSending:
case mautrix.EventStateLocalEcho:
return tcell.ColorGray
case ifc.MessageStateFailed:
case mautrix.EventStateSendFail:
return tcell.ColorRed
case ifc.MessageStateDefault:
case mautrix.EventStateDefault:
fallthrough
default:
return tcell.ColorDefault
@ -154,17 +171,6 @@ func (msg *BaseMessage) TimestampColor() tcell.Color {
return msg.getStateSpecificColor()
}
// Buffer returns the computed text buffer.
//
// The buffer contains the text of the message split into lines with a maximum
// width of whatever was provided to CalculateBuffer().
//
// N.B. This will NOT automatically calculate the buffer if it hasn't been
// calculated already, as that requires the target width.
func (msg *BaseMessage) Buffer() []tstring.TString {
return msg.buffer
}
// Height returns the number of rows in the computed buffer (see Buffer()).
func (msg *BaseMessage) Height() int {
return len(msg.buffer)
@ -197,15 +203,11 @@ func (msg *BaseMessage) Type() mautrix.MessageType {
return msg.MsgType
}
func (msg *BaseMessage) SetType(msgtype mautrix.MessageType) {
msg.MsgType = msgtype
}
func (msg *BaseMessage) State() ifc.MessageState {
func (msg *BaseMessage) State() mautrix.OutgoingEventState {
return msg.MsgState
}
func (msg *BaseMessage) SetState(state ifc.MessageState) {
func (msg *BaseMessage) SetState(state mautrix.OutgoingEventState) {
msg.MsgState = state
}
@ -225,6 +227,10 @@ func (msg *BaseMessage) SetIsService(isService bool) {
msg.MsgIsService = isService
}
func (msg *BaseMessage) Source() json.RawMessage {
return msg.MsgSource
}
func (msg *BaseMessage) Draw(screen mauview.Screen) {
for y, line := range msg.buffer {
line.Draw(screen, 0, y)

View File

@ -17,28 +17,21 @@
package messages
import (
"encoding/gob"
"time"
"maunium.net/go/mautrix"
"maunium.net/go/gomuks/config"
"maunium.net/go/gomuks/ui/messages/tstring"
)
func init() {
gob.Register(&ExpandedTextMessage{})
}
type ExpandedTextMessage struct {
BaseMessage
MsgText tstring.TString
}
// NewExpandedTextMessage creates a new ExpandedTextMessage object with the provided values and the default state.
func NewExpandedTextMessage(id, sender, displayname string, msgtype mautrix.MessageType, text tstring.TString, timestamp time.Time) UIMessage {
func NewExpandedTextMessage(event *mautrix.Event, displayname string, text tstring.TString) UIMessage {
return &ExpandedTextMessage{
BaseMessage: newBaseMessage(id, sender, displayname, msgtype, timestamp),
BaseMessage: newBaseMessage(event, displayname),
MsgText: text,
}
}

View File

@ -17,13 +17,12 @@
package messages
import (
"maunium.net/go/gomuks/ui/messages/html"
"maunium.net/go/mautrix"
"maunium.net/go/mauview"
"maunium.net/go/tcell"
"maunium.net/go/gomuks/config"
"maunium.net/go/gomuks/ui/messages/html"
)
type HTMLMessage struct {
@ -36,7 +35,7 @@ type HTMLMessage struct {
func NewHTMLMessage(event *mautrix.Event, displayname string, root html.Entity) UIMessage {
return &HTMLMessage{
BaseMessage: newBaseMessage(event.ID, event.Sender, displayname, event.Content.MsgType, unixToTime(event.Timestamp)),
BaseMessage: newBaseMessage(event, displayname),
Root: root,
}
}

View File

@ -18,10 +18,8 @@ package messages
import (
"bytes"
"encoding/gob"
"fmt"
"image/color"
"time"
"maunium.net/go/mautrix"
"maunium.net/go/tcell"
@ -33,10 +31,6 @@ import (
"maunium.net/go/gomuks/ui/messages/tstring"
)
func init() {
gob.Register(&ImageMessage{})
}
type ImageMessage struct {
BaseMessage
Body string
@ -48,9 +42,9 @@ type ImageMessage struct {
}
// NewImageMessage creates a new ImageMessage object with the provided values and the default state.
func NewImageMessage(matrix ifc.MatrixContainer, id, sender, displayname string, msgtype mautrix.MessageType, body, homeserver, fileID string, data []byte, timestamp time.Time) UIMessage {
func NewImageMessage(matrix ifc.MatrixContainer, event *mautrix.Event, displayname string, body, homeserver, fileID string, data []byte) UIMessage {
return &ImageMessage{
newBaseMessage(id, sender, displayname, msgtype, timestamp),
newBaseMessage(event, displayname),
body,
homeserver,
fileID,

View File

@ -20,12 +20,20 @@ import (
"maunium.net/go/gomuks/config"
"maunium.net/go/gomuks/interface"
"maunium.net/go/mauview"
"maunium.net/go/tcell"
)
// UIMessage is a wrapper for the content and metadata of a Matrix message intended to be displayed.
type UIMessage interface {
ifc.Message
Sender() string
SenderColor() tcell.Color
TextColor() tcell.Color
TimestampColor() tcell.Color
FormatTime() string
FormatDate() string
CalculateBuffer(preferences config.UserPreferences, width int)
Draw(screen mauview.Screen)
Height() int

View File

@ -1,71 +0,0 @@
// gomuks - A terminal Matrix client written in Go.
// Copyright (C) 2019 Tulir Asokan
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package messages
import (
"time"
"maunium.net/go/tcell"
)
// BasicMeta is a simple variable store implementation of MessageMeta.
type BasicMeta struct {
BSender string
BTimestamp time.Time
BSenderColor, BTextColor, BTimestampColor tcell.Color
}
// Sender gets the string that should be displayed as the sender of this message.
func (meta *BasicMeta) Sender() string {
return meta.BSender
}
func (meta *BasicMeta) SenderID() string {
return meta.BSender
}
// SenderColor returns the color the name of the sender should be shown in.
func (meta *BasicMeta) SenderColor() tcell.Color {
return meta.BSenderColor
}
// Timestamp returns the full time when the message was sent.
func (meta *BasicMeta) Timestamp() time.Time {
return meta.BTimestamp
}
// FormatTime returns the formatted time when the message was sent.
func (meta *BasicMeta) FormatTime() string {
return meta.BTimestamp.Format(TimeFormat)
}
// FormatDate returns the formatted date when the message was sent.
func (meta *BasicMeta) FormatDate() string {
return meta.BTimestamp.Format(DateFormat)
}
// TextColor returns the color the actual content of the message should be shown in.
func (meta *BasicMeta) TextColor() tcell.Color {
return meta.BTextColor
}
// TimestampColor returns the color the timestamp should be shown in.
//
// This usually does not apply to the date, as it is rendered separately from the message.
func (meta *BasicMeta) TimestampColor() tcell.Color {
return meta.BTimestampColor
}

View File

@ -18,9 +18,7 @@ package messages
import (
"fmt"
"html"
"strings"
"time"
"maunium.net/go/mautrix"
"maunium.net/go/tcell"
@ -28,12 +26,33 @@ import (
"maunium.net/go/gomuks/debug"
"maunium.net/go/gomuks/interface"
"maunium.net/go/gomuks/matrix/rooms"
"maunium.net/go/gomuks/ui/messages/html"
"maunium.net/go/gomuks/ui/messages/tstring"
"maunium.net/go/gomuks/ui/widget"
htmlp "maunium.net/go/gomuks/ui/messages/html"
)
func ParseEvent(matrix ifc.MatrixContainer, room *rooms.Room, evt *mautrix.Event) UIMessage {
msg := directParseEvent(matrix, room, evt)
if msg == nil {
return nil
}
if len(evt.Content.GetReplyTo()) > 0 {
roomID := evt.Content.RelatesTo.InReplyTo.RoomID
if len(roomID) == 0 {
roomID = room.ID
}
replyToEvt, _ := matrix.GetEvent(room, evt.Content.GetReplyTo())
if replyToEvt != nil {
// TODO add reply header
} else {
// TODO add unknown reply header
}
}
return msg
}
func directParseEvent(matrix ifc.MatrixContainer, room *rooms.Room, evt *mautrix.Event) UIMessage {
switch evt.Type {
case mautrix.EventSticker:
evt.Content.MsgType = mautrix.MsgImage
@ -48,14 +67,6 @@ func ParseEvent(matrix ifc.MatrixContainer, room *rooms.Room, evt *mautrix.Event
return nil
}
func unixToTime(unix int64) time.Time {
timestamp := time.Now()
if unix != 0 {
timestamp = time.Unix(unix/1000, unix%1000*1000)
}
return timestamp
}
func ParseStateEvent(matrix ifc.MatrixContainer, room *rooms.Room, evt *mautrix.Event) UIMessage {
displayname := evt.Sender
member := room.GetMember(evt.Sender)
@ -91,8 +102,7 @@ func ParseStateEvent(matrix ifc.MatrixContainer, room *rooms.Room, evt *mautrix.
case mautrix.StateAliases:
text = ParseAliasEvent(evt, displayname)
}
ts := unixToTime(evt.Timestamp)
return NewExpandedTextMessage(evt.ID, evt.Sender, displayname, mautrix.MessageType(evt.Type.Type), text, ts)
return NewExpandedTextMessage(evt, displayname, text)
}
func ParseMessage(matrix ifc.MatrixContainer, room *rooms.Room, evt *mautrix.Event) UIMessage {
@ -103,39 +113,20 @@ func ParseMessage(matrix ifc.MatrixContainer, room *rooms.Room, evt *mautrix.Eve
}
if len(evt.Content.GetReplyTo()) > 0 {
evt.Content.RemoveReplyFallback()
roomID := evt.Content.RelatesTo.InReplyTo.RoomID
if len(roomID) == 0 {
roomID = room.ID
}
replyToEvt, _ := matrix.GetEvent(room, evt.Content.GetReplyTo())
if replyToEvt != nil {
replyToEvt.Content.RemoveReplyFallback()
if len(replyToEvt.Content.FormattedBody) == 0 {
replyToEvt.Content.FormattedBody = html.EscapeString(replyToEvt.Content.Body)
}
evt.Content.FormattedBody = fmt.Sprintf(
"In reply to <a href='https://matrix.to/#/%[1]s'>%[1]s</a><blockquote>%[2]s</blockquote><br/><br/>%[3]s",
replyToEvt.Sender, replyToEvt.Content.FormattedBody, evt.Content.FormattedBody)
} else {
evt.Content.FormattedBody = fmt.Sprintf(
"In reply to unknown event https://matrix.to/#/%[1]s/%[2]s<br/>%[3]s",
roomID, evt.Content.GetReplyTo(), evt.Content.FormattedBody)
}
}
ts := unixToTime(evt.Timestamp)
switch evt.Content.MsgType {
case "m.text", "m.notice", "m.emote":
if evt.Content.Format == mautrix.FormatHTML {
return NewHTMLMessage(evt, displayname, htmlp.Parse(room, evt, displayname))
return NewHTMLMessage(evt, displayname, html.Parse(room, evt, displayname))
}
evt.Content.Body = strings.Replace(evt.Content.Body, "\t", " ", -1)
return NewTextMessage(evt.ID, evt.Sender, displayname, evt.Content.MsgType, evt.Content.Body, ts)
return NewTextMessage(evt, displayname, evt.Content.Body)
case "m.image":
data, hs, id, err := matrix.Download(evt.Content.URL)
if err != nil {
debug.Printf("Failed to download %s: %v", evt.Content.URL, err)
}
return NewImageMessage(matrix, evt.ID, evt.Sender, displayname, evt.Content.MsgType, evt.Content.Body, hs, id, data, ts)
return NewImageMessage(matrix, evt, displayname, evt.Content.Body, hs, id, data)
}
return nil
}
@ -220,8 +211,7 @@ func ParseMembershipEvent(room *rooms.Room, evt *mautrix.Event) UIMessage {
return nil
}
ts := unixToTime(evt.Timestamp)
return NewExpandedTextMessage(evt.ID, evt.Sender, displayname, "m.room.member", text, ts)
return NewExpandedTextMessage(evt, displayname, text)
}
func ParseAliasEvent(evt *mautrix.Event, displayname string) tstring.TString {

View File

@ -17,21 +17,14 @@
package messages
import (
"encoding/gob"
"fmt"
"time"
"maunium.net/go/mautrix"
"maunium.net/go/gomuks/config"
"maunium.net/go/gomuks/interface"
"maunium.net/go/gomuks/ui/messages/tstring"
)
func init() {
gob.Register(&TextMessage{})
}
type TextMessage struct {
BaseMessage
cache tstring.TString
@ -39,9 +32,9 @@ type TextMessage struct {
}
// NewTextMessage creates a new UITextMessage object with the provided values and the default state.
func NewTextMessage(id, sender, displayname string, msgtype mautrix.MessageType, text string, timestamp time.Time) UIMessage {
func NewTextMessage(event *mautrix.Event, displayname string, text string) UIMessage {
return &TextMessage{
BaseMessage: newBaseMessage(id, sender, displayname, msgtype, timestamp),
BaseMessage: newBaseMessage(event, displayname),
MsgText: text,
}
}
@ -59,16 +52,6 @@ func (msg *TextMessage) getCache() tstring.TString {
return msg.cache
}
func (msg *TextMessage) SetType(msgtype mautrix.MessageType) {
msg.BaseMessage.SetType(msgtype)
msg.cache = nil
}
func (msg *TextMessage) SetState(state ifc.MessageState) {
msg.BaseMessage.SetState(state)
msg.cache = nil
}
func (msg *TextMessage) SetIsHighlight(isHighlight bool) {
msg.BaseMessage.SetIsHighlight(isHighlight)
msg.cache = nil

View File

@ -438,3 +438,7 @@ func (view *RoomView) AddServiceMessage(text string) {
func (view *RoomView) AddMessage(message ifc.Message, direction ifc.MessageDirection) {
view.content.AddMessage(message, direction)
}
func (view *RoomView) ParseEvent(evt *mautrix.Event) ifc.Message {
return messages.ParseEvent(view.parent.matrix, view.Room, evt)
}

View File

@ -35,7 +35,6 @@ import (
"maunium.net/go/gomuks/lib/notification"
"maunium.net/go/gomuks/matrix/pushrules"
"maunium.net/go/gomuks/matrix/rooms"
"maunium.net/go/gomuks/ui/messages"
"maunium.net/go/gomuks/ui/widget"
)
@ -166,11 +165,10 @@ func (view *MainView) InputSubmit(roomView *RoomView, text string) {
}
func (view *MainView) SendMessage(roomView *RoomView, text string) {
tempMessage := roomView.NewTempMessage("m.text", text)
go view.sendTempMessage(roomView, tempMessage, text)
go view.goSendMessage(roomView, text)
}
func (view *MainView) sendTempMessage(roomView *RoomView, tempMessage ifc.Message, text string) {
func (view *MainView) goSendMessage(roomView *RoomView, text string) {
defer debug.Recover()
debug.Print("Sending message", tempMessage.Type(), text)
if !roomView.config.Preferences.DisableEmojis {
@ -317,11 +315,10 @@ func (view *MainView) addRoomPage(room *rooms.Room) {
view.rooms[room.ID] = roomView
roomView.UpdateUserList()
// FIXME
/*_, err := roomView.LoadHistory(view.matrix, view.config.HistoryDir)
if err != nil {
debug.Printf("Failed to load history of %s: %v", roomView.Room.GetTitle(), err)
}*/
// TODO make sure this works
if len(roomView.MessageView().messages) == 0 {
go view.LoadHistory(room.ID)
}
}
}
@ -428,10 +425,11 @@ func (view *MainView) NotifyMessage(room *rooms.Room, message ifc.Message, shoul
if shouldNotify && !recentlyFocused {
// Push rules say notify and the terminal is not focused, send desktop notification.
shouldPlaySound := should.PlaySound && should.SoundName == "default"
sendNotification(room, message.Sender(), message.NotificationContent(), should.Highlight, shouldPlaySound)
sendNotification(room, message.NotificationSenderName(), message.NotificationContent(), should.Highlight, shouldPlaySound)
}
message.SetIsHighlight(should.Highlight)
// TODO Make sure this happens somewhere else
//message.SetIsHighlight(should.Highlight)
}
func (view *MainView) InitialSyncDone() {
@ -470,7 +468,7 @@ func (view *MainView) LoadHistory(room string) {
return
}
for _, evt := range history {
message := view.ParseEvent(roomView, evt)
message := roomView.ParseEvent(evt)
if message != nil {
roomView.AddMessage(message, ifc.PrependMessage)
}
@ -483,7 +481,3 @@ func (view *MainView) LoadHistory(room string) {
view.config.PutRoom(roomView.Room)
view.parent.Render()
}
func (view *MainView) ParseEvent(roomView ifc.RoomView, evt *mautrix.Event) ifc.Message {
return messages.ParseEvent(view.matrix, roomView.MxRoom(), evt)
}