Fix bugs and add MessageView widget
This commit is contained in:
		@@ -160,7 +160,7 @@ func (c *MatrixContainer) HandleMessage(evt *gomatrix.Event) {
 | 
			
		||||
		timestamp = time.Unix(timestampInt64/1000, timestampInt64%1000*1000)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	c.ui.MainView().AddMessage(evt.RoomID, evt.Sender, message, timestamp)
 | 
			
		||||
	c.ui.MainView().AddRealMessage(evt.RoomID, evt.ID, evt.Sender, message, timestamp)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *MatrixContainer) HandleMembership(evt *gomatrix.Event) {
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										260
									
								
								message-view.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										260
									
								
								message-view.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,260 @@
 | 
			
		||||
// gomuks - A terminal Matrix client written in Go.
 | 
			
		||||
// Copyright (C) 2018 Tulir Asokan
 | 
			
		||||
//
 | 
			
		||||
// This program is free software: you can redistribute it and/or modify
 | 
			
		||||
// it under the terms of the GNU 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 General Public License for more details.
 | 
			
		||||
//
 | 
			
		||||
// You should have received a copy of the GNU General Public License
 | 
			
		||||
// along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
 | 
			
		||||
package main
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"regexp"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/gdamore/tcell"
 | 
			
		||||
	"github.com/mattn/go-runewidth"
 | 
			
		||||
	"maunium.net/go/tview"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type Message struct {
 | 
			
		||||
	ID           string
 | 
			
		||||
	Sender       string
 | 
			
		||||
	Text         string
 | 
			
		||||
	Timestamp    string
 | 
			
		||||
	RenderSender bool
 | 
			
		||||
 | 
			
		||||
	buffer      []string
 | 
			
		||||
	senderColor tcell.Color
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
	boundaryPattern = regexp.MustCompile("([[:punct:]]\\s*|\\s+)")
 | 
			
		||||
	spacePattern    = regexp.MustCompile(`\s+`)
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func (message *Message) calculateBuffer(width int) {
 | 
			
		||||
	if width < 1 {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	message.buffer = []string{}
 | 
			
		||||
	forcedLinebreaks := strings.Split(message.Text, "\n")
 | 
			
		||||
	newlines := 0
 | 
			
		||||
	for _, str := range forcedLinebreaks {
 | 
			
		||||
		if len(str) == 0 && newlines < 1 {
 | 
			
		||||
			message.buffer = append(message.buffer, "")
 | 
			
		||||
			newlines++
 | 
			
		||||
		} else {
 | 
			
		||||
			newlines = 0
 | 
			
		||||
		}
 | 
			
		||||
		// From tview/textview.go#reindexBuffer()
 | 
			
		||||
		for len(str) > 0 {
 | 
			
		||||
			extract := runewidth.Truncate(str, width, "")
 | 
			
		||||
			if len(extract) < len(str) {
 | 
			
		||||
				if spaces := spacePattern.FindStringIndex(str[len(extract):]); spaces != nil && spaces[0] == 0 {
 | 
			
		||||
					extract = str[:len(extract)+spaces[1]]
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				matches := boundaryPattern.FindAllStringIndex(extract, -1)
 | 
			
		||||
				if len(matches) > 0 {
 | 
			
		||||
					extract = extract[:matches[len(matches)-1][1]]
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			message.buffer = append(message.buffer, extract)
 | 
			
		||||
			str = str[len(extract):]
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type MessageView struct {
 | 
			
		||||
	*tview.Box
 | 
			
		||||
 | 
			
		||||
	ScrollOffset    int
 | 
			
		||||
	MaxSenderWidth  int
 | 
			
		||||
	TimestampFormat string
 | 
			
		||||
	TimestampWidth  int
 | 
			
		||||
	Separator       rune
 | 
			
		||||
 | 
			
		||||
	widestSender        int
 | 
			
		||||
	prevWidth           int
 | 
			
		||||
	prevHeight          int
 | 
			
		||||
	prevScrollOffset    int
 | 
			
		||||
	firstDisplayMessage int
 | 
			
		||||
	lastDisplayMessage  int
 | 
			
		||||
	totalHeight         int
 | 
			
		||||
 | 
			
		||||
	messages []*Message
 | 
			
		||||
 | 
			
		||||
	debug DebugPrinter
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NewMessageView(debug DebugPrinter) *MessageView {
 | 
			
		||||
	return &MessageView{
 | 
			
		||||
		Box:             tview.NewBox(),
 | 
			
		||||
		MaxSenderWidth:  20,
 | 
			
		||||
		TimestampFormat: "15:04:05",
 | 
			
		||||
		TimestampWidth:  8,
 | 
			
		||||
		Separator:       '|',
 | 
			
		||||
		ScrollOffset:    0,
 | 
			
		||||
 | 
			
		||||
		widestSender:        5,
 | 
			
		||||
		prevWidth:           -1,
 | 
			
		||||
		prevHeight:          -1,
 | 
			
		||||
		prevScrollOffset:    -1,
 | 
			
		||||
		firstDisplayMessage: -1,
 | 
			
		||||
		lastDisplayMessage:  -1,
 | 
			
		||||
		totalHeight:         -1,
 | 
			
		||||
 | 
			
		||||
		debug: debug,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (view *MessageView) recalculateBuffers(width int) {
 | 
			
		||||
	width -= view.TimestampWidth + TimestampSenderGap + view.widestSender + SenderMessageGap
 | 
			
		||||
	for _, message := range view.messages {
 | 
			
		||||
		message.calculateBuffer(width)
 | 
			
		||||
	}
 | 
			
		||||
	view.prevWidth = width
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (view *MessageView) AddMessage(id, sender, text string, timestamp time.Time) {
 | 
			
		||||
	if len(sender) > view.widestSender {
 | 
			
		||||
		view.widestSender = len(sender)
 | 
			
		||||
		if view.widestSender > view.MaxSenderWidth {
 | 
			
		||||
			view.widestSender = view.MaxSenderWidth
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	message := &Message{
 | 
			
		||||
		ID:           id,
 | 
			
		||||
		Sender:       sender,
 | 
			
		||||
		RenderSender: true,
 | 
			
		||||
		Text:         text,
 | 
			
		||||
		Timestamp:    timestamp.Format(view.TimestampFormat),
 | 
			
		||||
		senderColor:  getColor(sender),
 | 
			
		||||
	}
 | 
			
		||||
	_, _, width, height := view.GetInnerRect()
 | 
			
		||||
	width -= view.TimestampWidth + TimestampSenderGap + view.widestSender + SenderMessageGap
 | 
			
		||||
	message.calculateBuffer(width)
 | 
			
		||||
	if view.ScrollOffset > 0 {
 | 
			
		||||
		view.ScrollOffset += len(message.buffer)
 | 
			
		||||
	}
 | 
			
		||||
	if len(view.messages) > 0 && view.messages[len(view.messages)-1].Sender == message.Sender {
 | 
			
		||||
		message.RenderSender = false
 | 
			
		||||
	}
 | 
			
		||||
	view.messages = append(view.messages, message)
 | 
			
		||||
	view.recalculateHeight(height)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (view *MessageView) recalculateHeight(height int) {
 | 
			
		||||
	view.firstDisplayMessage = -1
 | 
			
		||||
	view.lastDisplayMessage = -1
 | 
			
		||||
	view.totalHeight = 0
 | 
			
		||||
	for i := len(view.messages) - 1; i >= 0; i-- {
 | 
			
		||||
		prevTotalHeight := view.totalHeight
 | 
			
		||||
		view.totalHeight += len(view.messages[i].buffer)
 | 
			
		||||
 | 
			
		||||
		if view.totalHeight < view.ScrollOffset {
 | 
			
		||||
			continue
 | 
			
		||||
		} else if view.firstDisplayMessage == -1 {
 | 
			
		||||
			view.lastDisplayMessage = i
 | 
			
		||||
			view.firstDisplayMessage = i
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if prevTotalHeight < height+view.ScrollOffset {
 | 
			
		||||
			view.lastDisplayMessage = i
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	view.prevScrollOffset = view.ScrollOffset
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (view *MessageView) PageUp() {
 | 
			
		||||
	_, _, _, height := view.GetInnerRect()
 | 
			
		||||
	view.ScrollOffset += height / 2
 | 
			
		||||
	if view.ScrollOffset > view.totalHeight-height {
 | 
			
		||||
		view.ScrollOffset = view.totalHeight - height + 5
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (view *MessageView) PageDown() {
 | 
			
		||||
	_, _, _, height := view.GetInnerRect()
 | 
			
		||||
	view.ScrollOffset -= height / 2
 | 
			
		||||
	if view.ScrollOffset < 0 {
 | 
			
		||||
		view.ScrollOffset = 0
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (view *MessageView) writeLine(screen tcell.Screen, line string, x, y int, color tcell.Color) {
 | 
			
		||||
	offsetX := 0
 | 
			
		||||
	for _, ch := range line {
 | 
			
		||||
		chWidth := runewidth.RuneWidth(ch)
 | 
			
		||||
		if chWidth == 0 {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		for localOffset := 0; localOffset < chWidth; localOffset++ {
 | 
			
		||||
			screen.SetContent(x+offsetX+localOffset, y, ch, nil, tcell.StyleDefault.Foreground(color))
 | 
			
		||||
		}
 | 
			
		||||
		offsetX += chWidth
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	TimestampSenderGap = 1
 | 
			
		||||
	SenderSeparatorGap = 1
 | 
			
		||||
	SenderMessageGap   = 3
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func (view *MessageView) Draw(screen tcell.Screen) {
 | 
			
		||||
	view.Box.Draw(screen)
 | 
			
		||||
 | 
			
		||||
	x, y, width, height := view.GetInnerRect()
 | 
			
		||||
	if width != view.prevWidth {
 | 
			
		||||
		view.recalculateBuffers(width)
 | 
			
		||||
	}
 | 
			
		||||
	if height != view.prevHeight || width != view.prevWidth || view.ScrollOffset != view.prevScrollOffset {
 | 
			
		||||
		view.recalculateHeight(height)
 | 
			
		||||
	}
 | 
			
		||||
	usernameOffsetX := view.TimestampWidth + TimestampSenderGap
 | 
			
		||||
	messageOffsetX := usernameOffsetX + view.widestSender + SenderMessageGap
 | 
			
		||||
 | 
			
		||||
	separatorX := x + usernameOffsetX + view.widestSender + SenderSeparatorGap
 | 
			
		||||
	for separatorY := y; separatorY < y+height; separatorY++ {
 | 
			
		||||
		screen.SetContent(separatorX, separatorY, view.Separator, nil, tcell.StyleDefault)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if view.firstDisplayMessage == -1 || view.lastDisplayMessage == -1 {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	writeOffset := 0
 | 
			
		||||
	for i := view.firstDisplayMessage; i >= view.lastDisplayMessage; i-- {
 | 
			
		||||
		message := view.messages[i]
 | 
			
		||||
		messageHeight := len(message.buffer)
 | 
			
		||||
 | 
			
		||||
		senderAtLine := y + height - writeOffset - messageHeight
 | 
			
		||||
		if senderAtLine < y {
 | 
			
		||||
			senderAtLine = y
 | 
			
		||||
		}
 | 
			
		||||
		view.writeLine(screen, message.Timestamp, x, senderAtLine, tcell.ColorDefault)
 | 
			
		||||
		if message.RenderSender || i == view.lastDisplayMessage {
 | 
			
		||||
			view.writeLine(screen, message.Sender, x+usernameOffsetX, senderAtLine, message.senderColor)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		for num, line := range message.buffer {
 | 
			
		||||
			offsetY := height - messageHeight - writeOffset + num
 | 
			
		||||
			if offsetY >= 0 {
 | 
			
		||||
				view.writeLine(screen, line, x+messageOffsetX, y+offsetY, tcell.ColorDefault)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		writeOffset += messageHeight
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										40
									
								
								room-view.go
									
									
									
									
									
								
							
							
						
						
									
										40
									
								
								room-view.go
									
									
									
									
									
								
							@@ -19,9 +19,8 @@ package main
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"hash/fnv"
 | 
			
		||||
	"regexp"
 | 
			
		||||
	"sort"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/gdamore/tcell"
 | 
			
		||||
	"maunium.net/go/gomatrix"
 | 
			
		||||
@@ -32,10 +31,12 @@ type RoomView struct {
 | 
			
		||||
	*tview.Box
 | 
			
		||||
 | 
			
		||||
	topic    *tview.TextView
 | 
			
		||||
	content  *tview.TextView
 | 
			
		||||
	content  *MessageView
 | 
			
		||||
	status   *tview.TextView
 | 
			
		||||
	userList *tview.TextView
 | 
			
		||||
	room     *gomatrix.Room
 | 
			
		||||
 | 
			
		||||
	debug DebugPrinter
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var colorNames []string
 | 
			
		||||
@@ -47,27 +48,30 @@ func init() {
 | 
			
		||||
		colorNames[i] = name
 | 
			
		||||
		i++
 | 
			
		||||
	}
 | 
			
		||||
	sort.Sort(sort.StringSlice(colorNames))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NewRoomView(room *gomatrix.Room) *RoomView {
 | 
			
		||||
func NewRoomView(debug DebugPrinter, room *gomatrix.Room) *RoomView {
 | 
			
		||||
	view := &RoomView{
 | 
			
		||||
		Box:      tview.NewBox(),
 | 
			
		||||
		topic:    tview.NewTextView(),
 | 
			
		||||
		content:  tview.NewTextView(),
 | 
			
		||||
		content:  NewMessageView(debug),
 | 
			
		||||
		status:   tview.NewTextView(),
 | 
			
		||||
		userList: tview.NewTextView(),
 | 
			
		||||
		room:     room,
 | 
			
		||||
		debug:    debug,
 | 
			
		||||
	}
 | 
			
		||||
	view.topic.
 | 
			
		||||
		SetText(strings.Replace(room.GetTopic(), "\n", " ", -1)).
 | 
			
		||||
		SetBackgroundColor(tcell.ColorDarkGreen)
 | 
			
		||||
	view.status.SetBackgroundColor(tcell.ColorDimGray)
 | 
			
		||||
	view.userList.SetDynamicColors(true)
 | 
			
		||||
	view.content.SetDynamicColors(true)
 | 
			
		||||
	return view
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (view *RoomView) Draw(screen tcell.Screen) {
 | 
			
		||||
	view.Box.Draw(screen)
 | 
			
		||||
 | 
			
		||||
	x, y, width, height := view.GetRect()
 | 
			
		||||
	view.topic.SetRect(x, y, width, 1)
 | 
			
		||||
	view.content.SetRect(x, y+1, width-30, height-2)
 | 
			
		||||
@@ -104,26 +108,24 @@ func (view *RoomView) SetTyping(users []string) {
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var colorPattern = regexp.MustCompile(`\[([a-zA-Z]+|#[0-9a-zA-Z]{6})\]`)
 | 
			
		||||
func (view *RoomView) MessageView() *MessageView {
 | 
			
		||||
	return view.content
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func color(s string) string {
 | 
			
		||||
func getColorName(s string) string {
 | 
			
		||||
	h := fnv.New32a()
 | 
			
		||||
	h.Write([]byte(s))
 | 
			
		||||
	color := colorNames[int(h.Sum32())%len(colorNames)]
 | 
			
		||||
	return fmt.Sprintf("[%s]%s[white]", color, s)
 | 
			
		||||
	return colorNames[int(h.Sum32())%len(colorNames)]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func escapeColor(s string) string {
 | 
			
		||||
	return colorPattern.ReplaceAllString(s, "[$1[]")
 | 
			
		||||
func getColor(s string) tcell.Color {
 | 
			
		||||
	h := fnv.New32a()
 | 
			
		||||
	h.Write([]byte(s))
 | 
			
		||||
	return tcell.ColorNames[colorNames[int(h.Sum32())%len(colorNames)]]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (view *RoomView) AddMessage(sender, message string, timestamp time.Time) {
 | 
			
		||||
	member := view.room.GetMember(sender)
 | 
			
		||||
	if member != nil {
 | 
			
		||||
		sender = member.DisplayName
 | 
			
		||||
	}
 | 
			
		||||
	fmt.Fprintf(view.content, "[%s] %s: %s\n",
 | 
			
		||||
		timestamp.Format("15:04:05"), color(sender), escapeColor(message))
 | 
			
		||||
func color(s string) string {
 | 
			
		||||
	return fmt.Sprintf("[%s]%s[white]", getColorName(s), s)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (view *RoomView) UpdateUserList() {
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										32
									
								
								view-main.go
									
									
									
									
									
								
							
							
						
						
									
										32
									
								
								view-main.go
									
									
									
									
									
								
							@@ -126,28 +126,34 @@ func (view *MainView) HandleCommand(room, command string, args []string) {
 | 
			
		||||
		view.matrix.client.LeaveRoom(room)
 | 
			
		||||
	case "/join":
 | 
			
		||||
		if len(args) == 0 {
 | 
			
		||||
			view.AddMessage(room, "*", "Usage: /join <room>", time.Now())
 | 
			
		||||
			view.AddMessage(room,  "Usage: /join <room>")
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
		view.debug.Print(view.matrix.JoinRoom(args[0]))
 | 
			
		||||
	default:
 | 
			
		||||
		view.AddMessage(room, "*", "Unknown command.", time.Now())
 | 
			
		||||
		view.AddMessage(room, "Unknown command.")
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (view *MainView) InputCapture(key *tcell.EventKey) *tcell.EventKey {
 | 
			
		||||
	k := key.Key()
 | 
			
		||||
	if key.Modifiers() == tcell.ModCtrl {
 | 
			
		||||
		if key.Key() == tcell.KeyDown {
 | 
			
		||||
		if k == tcell.KeyDown {
 | 
			
		||||
			view.SwitchRoom(view.currentRoomIndex + 1)
 | 
			
		||||
			view.roomList.SetCurrentItem(view.currentRoomIndex)
 | 
			
		||||
		} else if key.Key() == tcell.KeyUp {
 | 
			
		||||
		} else if k == tcell.KeyUp {
 | 
			
		||||
			view.SwitchRoom(view.currentRoomIndex - 1)
 | 
			
		||||
			view.roomList.SetCurrentItem(view.currentRoomIndex)
 | 
			
		||||
		} else {
 | 
			
		||||
			return key
 | 
			
		||||
		}
 | 
			
		||||
	} else if key.Key() == tcell.KeyPgUp || key.Key() == tcell.KeyPgDn {
 | 
			
		||||
		view.rooms[view.CurrentRoomID()].InputHandler()(key, nil)
 | 
			
		||||
	} else if k == tcell.KeyPgUp || k == tcell.KeyPgDn {
 | 
			
		||||
		msgView := view.rooms[view.CurrentRoomID()].MessageView()
 | 
			
		||||
		if k == tcell.KeyPgUp {
 | 
			
		||||
			msgView.PageUp()
 | 
			
		||||
		} else {
 | 
			
		||||
			msgView.PageDown()
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		return key
 | 
			
		||||
	}
 | 
			
		||||
@@ -178,7 +184,7 @@ func (view *MainView) addRoom(index int, room string) {
 | 
			
		||||
		view.SwitchRoom(index)
 | 
			
		||||
	})
 | 
			
		||||
	if !view.roomView.HasPage(room) {
 | 
			
		||||
		roomView := NewRoomView(roomStore)
 | 
			
		||||
		roomView := NewRoomView(view.debug, roomStore)
 | 
			
		||||
		view.rooms[room] = roomView
 | 
			
		||||
		view.roomView.AddPage(room, roomView, true, false)
 | 
			
		||||
		roomView.UpdateUserList()
 | 
			
		||||
@@ -231,10 +237,18 @@ func (view *MainView) SetTyping(room string, users []string) {
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (view *MainView) AddMessage(room, sender, message string, timestamp time.Time) {
 | 
			
		||||
func (view *MainView) AddMessage(room, message string) {
 | 
			
		||||
	view.AddRealMessage(room, "", "*", message, time.Now())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (view *MainView) AddRealMessage(room, id, sender, message string, timestamp time.Time) {
 | 
			
		||||
	roomView, ok := view.rooms[room]
 | 
			
		||||
	if ok {
 | 
			
		||||
		roomView.AddMessage(sender, message, timestamp)
 | 
			
		||||
		member := roomView.room.GetMember(sender)
 | 
			
		||||
		if member != nil {
 | 
			
		||||
			sender = member.DisplayName
 | 
			
		||||
		}
 | 
			
		||||
		roomView.content.AddMessage(id, sender, message, timestamp)
 | 
			
		||||
		view.parent.Render()
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user