feat(messageslist)!: group consecutive messages by the same author
This commit is contained in:
parent
3562b4d546
commit
60aa5f52f1
6 changed files with 168 additions and 38 deletions
|
|
@ -361,9 +361,9 @@ Default: `"box_drawing"`
|
|||
|
||||
Default: `false`
|
||||
|
||||
### ui.messages_list.gap
|
||||
### ui.messages_list.spacing
|
||||
|
||||
The number of empty rows between messages.
|
||||
The number of empty rows between messages, except those of a group.
|
||||
|
||||
Default: `1`
|
||||
|
||||
|
|
@ -412,6 +412,18 @@ The number of rows an avatar takes. It is recommended to use `1` with compact.
|
|||
|
||||
Default: `2`
|
||||
|
||||
### ui.messages_list.grouping
|
||||
|
||||
#### ui.messages_list.grouping.enabled
|
||||
|
||||
Default: `true`
|
||||
|
||||
#### ui.messages_list.grouping.interval
|
||||
|
||||
The longest time between two messages by the same author for them to share a header.
|
||||
|
||||
Default: `"10m0s"`
|
||||
|
||||
## ui.mentions_list
|
||||
|
||||
The list of mention suggestions above the composer. Its width and height are capped to the available space.
|
||||
|
|
|
|||
2
go.mod
2
go.mod
|
|
@ -15,7 +15,7 @@ require (
|
|||
github.com/andybalholm/brotli v1.2.6
|
||||
github.com/ayn2op/arikawa/v3 v3.0.0-20261014035141-32b58d8f3755
|
||||
github.com/ayn2op/ningen/v3 v3.0.1-0.20261014035153-5e43590a1426
|
||||
github.com/ayn2op/tview v0.0.0-20261015202917-18bf0788a772
|
||||
github.com/ayn2op/tview v0.0.0-20261015215920-c33abb9e4eb2
|
||||
github.com/bogdanfinn/fhttp v0.6.9
|
||||
github.com/bogdanfinn/tls-client v1.16.0
|
||||
github.com/gdamore/tcell/v3 v3.6.0
|
||||
|
|
|
|||
4
go.sum
4
go.sum
|
|
@ -14,8 +14,8 @@ github.com/ayn2op/arikawa/v3 v3.0.0-20261014035141-32b58d8f3755 h1:XHo9kizImaAEt
|
|||
github.com/ayn2op/arikawa/v3 v3.0.0-20261014035141-32b58d8f3755/go.mod h1:YARcqZmk7hDjXB+lexlXX4wDFHULafbGnF0gsD0tuNw=
|
||||
github.com/ayn2op/ningen/v3 v3.0.1-0.20261014035153-5e43590a1426 h1:0ndRWpAiSSgR0z9b7K+pwMlCHEk3VzQeS5ls+zWtbr0=
|
||||
github.com/ayn2op/ningen/v3 v3.0.1-0.20261014035153-5e43590a1426/go.mod h1:4S2B9frwrjELeAldoqPgasA/VfnOySgRlektwYLuVks=
|
||||
github.com/ayn2op/tview v0.0.0-20261015202917-18bf0788a772 h1:XXNOUzYpwH2S/Q5XA/kHJCkbz4HwXtM/Unh3HbhqRIw=
|
||||
github.com/ayn2op/tview v0.0.0-20261015202917-18bf0788a772/go.mod h1:3aWH6S5uOqOkZv7JFePEVHukTj9hPb6Lbynm59Sks2E=
|
||||
github.com/ayn2op/tview v0.0.0-20261015215920-c33abb9e4eb2 h1:uGLp4qH9uegcYax3fruZ+FyadoXZpEIyYova0gK8nkM=
|
||||
github.com/ayn2op/tview v0.0.0-20261015215920-c33abb9e4eb2/go.mod h1:3aWH6S5uOqOkZv7JFePEVHukTj9hPb6Lbynm59Sks2E=
|
||||
github.com/bdandy/go-errors v1.2.2 h1:WdFv/oukjTJCLa79UfkGmwX7ZxONAihKu4V0mLIs11Q=
|
||||
github.com/bdandy/go-errors v1.2.2/go.mod h1:NkYHl4Fey9oRRdbB1CoC6e84tuqQHiqrOcZpqFEkBxM=
|
||||
github.com/bdandy/go-socks4 v1.2.3 h1:Q6Y2heY1GRjCtHbmlKfnwrKVU/k81LS8mRGLRlmDlic=
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package config
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/ayn2op/tview"
|
||||
"github.com/ayn2op/tview/scrollbar"
|
||||
|
|
@ -168,14 +169,22 @@ type (
|
|||
|
||||
MessagesListUIConfig struct {
|
||||
Compact bool `toml:"compact"`
|
||||
// The number of empty rows between messages.
|
||||
Gap uint8 `toml:"gap"`
|
||||
// The number of empty rows between messages, except those of a group.
|
||||
Spacing uint8 `toml:"spacing"`
|
||||
|
||||
ReplyIndicator string `toml:"reply_indicator"`
|
||||
ForwardedIndicator string `toml:"forwarded_indicator"`
|
||||
|
||||
Attachments AttachmentsUIConfig `toml:"attachments"`
|
||||
Avatars AvatarsUIConfig `toml:"avatars"`
|
||||
Grouping GroupingUIConfig `toml:"grouping"`
|
||||
}
|
||||
|
||||
// GroupingUIConfig shows consecutive messages by the same author under one header.
|
||||
GroupingUIConfig struct {
|
||||
Enabled bool `toml:"enabled"`
|
||||
// The longest time between two messages by the same author for them to share a header.
|
||||
Interval time.Duration `toml:"interval"`
|
||||
}
|
||||
|
||||
AvatarsUIConfig struct {
|
||||
|
|
@ -221,7 +230,7 @@ func defaultUI() UIConfig {
|
|||
GuildsTree: GuildsTreeUIConfig{TreeUIConfig{Graphics: true}, true},
|
||||
MembersTree: TreeUIConfig{Graphics: true},
|
||||
ScrollBar: ScrollBarUIConfig{SymbolSet: SymbolSetWrapper{boxDrawing}, Visibility: ScrollBarVisibilityWrapper{viewport.ScrollBarVisibilityAutomatic}},
|
||||
MessagesList: MessagesListUIConfig{Gap: 1, ReplyIndicator: ">", ForwardedIndicator: "<", Attachments: attachments, Avatars: AvatarsUIConfig{Preview: true, Height: 2}},
|
||||
MessagesList: MessagesListUIConfig{Spacing: 1, ReplyIndicator: ">", ForwardedIndicator: "<", Attachments: attachments, Avatars: AvatarsUIConfig{Preview: true, Height: 2}, Grouping: GroupingUIConfig{Enabled: true, Interval: 10 * time.Minute}},
|
||||
MentionsList: MentionsListUIConfig{MinWidth: 20},
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ import (
|
|||
"github.com/ayn2op/tview/row"
|
||||
"github.com/ayn2op/tview/scrollbar"
|
||||
"github.com/ayn2op/tview/slider"
|
||||
"github.com/ayn2op/tview/spacer"
|
||||
"github.com/ayn2op/tview/text"
|
||||
"github.com/ayn2op/tview/textview"
|
||||
"github.com/ayn2op/tview/viewport"
|
||||
|
|
@ -50,6 +51,8 @@ type messageItem struct {
|
|||
text, reply *messageView
|
||||
// A separator shows the date of the message after it.
|
||||
separator bool
|
||||
// grouped reports whether the message is drawn without a header, under that of the message before it.
|
||||
grouped bool
|
||||
|
||||
// A preview is blank while loading and empty if loading failed.
|
||||
previews map[discord.URL]tviewimage.Widget
|
||||
|
|
@ -122,7 +125,13 @@ func (m Model) listView(focused bool) viewport.Widget {
|
|||
ThumbStyle(theme.ThumbStyle.Style)
|
||||
// The model moves the selection itself, so the list is not focused and only takes clicks.
|
||||
l := list.New(m.cursor, len(m.items), m.buildItem).
|
||||
Gap(int(m.cfg.UI.MessagesList.Gap)).
|
||||
// A group's messages have no spacing between them.
|
||||
Spacing(func(index int) int {
|
||||
if m.items[index].grouped {
|
||||
return 0
|
||||
}
|
||||
return int(m.cfg.UI.MessagesList.Spacing)
|
||||
}).
|
||||
SelectedStyle(m.cfg.Theme.MessagesList.SelectedMessageStyle.Style).
|
||||
OnChange(func(cursor int) tview.Msg { return listMsg(cursor) })
|
||||
return viewport.New(l, m.scrollState).
|
||||
|
|
@ -206,10 +215,12 @@ func (m *Model) setMessages(messages []discord.Message) {
|
|||
|
||||
func (m *Model) AddMessage(message discord.Message) tview.Cmd {
|
||||
// Appending keeps the items before it as they are, so only the separator of a new day is added rather than all rebuilt.
|
||||
if m.cfg.DateSeparator.Enabled && (len(m.items) == 0 || !sameLocalDate(m.items[len(m.items)-1].message.Timestamp, message.Timestamp)) {
|
||||
last := len(m.items) - 1
|
||||
separator := m.cfg.DateSeparator.Enabled && (last < 0 || !sameLocalDate(m.items[last].message.Timestamp, message.Timestamp))
|
||||
if separator {
|
||||
m.items = append(m.items, messageItem{separator: true})
|
||||
}
|
||||
m.items = append(m.items, messageItem{message: message})
|
||||
m.items = append(m.items, messageItem{message: message, grouped: last >= 0 && !separator && m.grouped(m.items[last].message, message)})
|
||||
return m.loadPreviews()
|
||||
}
|
||||
|
||||
|
|
@ -258,7 +269,8 @@ func (m Model) buildItem(index int) tview.Widget {
|
|||
|
||||
message, style := item.message, m.cfg.Theme.MessagesList.MessageStyle.Style
|
||||
if item.text == nil {
|
||||
item.text = &messageView{render: func(width int) richtext.Text { return m.renderMessage(message, style, width) }}
|
||||
grouped := item.grouped
|
||||
item.text = &messageView{render: func(width int) richtext.Text { return m.renderMessage(message, style, width, grouped) }}
|
||||
item.reply = &messageView{render: func(int) richtext.Text {
|
||||
builder := new(richtext.Builder)
|
||||
m.drawReply(builder, message, style)
|
||||
|
|
@ -278,7 +290,13 @@ func (m Model) buildItem(index int) tview.Widget {
|
|||
}
|
||||
|
||||
if avatar, ok := m.avatars[avatarURL(message.Author)]; ok {
|
||||
body = row.New(avatar, body).Spacing(1).Height(layout.Shrink)
|
||||
var gutter tview.Widget = avatar
|
||||
// A grouped message leaves the avatar's column empty, under the avatar of the group.
|
||||
if item.grouped {
|
||||
width, _ := avatar.Size()
|
||||
gutter = spacer.New().Width(width)
|
||||
}
|
||||
body = row.New(gutter, body).Spacing(1).Height(layout.Shrink)
|
||||
}
|
||||
|
||||
if message.Type == discord.InlinedReplyMessage && !m.blocked(message.Author) {
|
||||
|
|
@ -351,24 +369,46 @@ func (d dateSeparator) Draw(screen tview.Screen, area tview.Rectangle) {
|
|||
|
||||
func (dateSeparator) Handle(msg tview.Msg, area tview.Rectangle) tview.Msg { return msg }
|
||||
|
||||
func (m Model) renderMessage(message discord.Message, baseStyle tview.Style, width int) richtext.Text {
|
||||
func (m Model) renderMessage(message discord.Message, baseStyle tview.Style, width int, grouped bool) richtext.Text {
|
||||
builder := new(richtext.Builder)
|
||||
m.writeMessage(builder, message, baseStyle, width)
|
||||
return builder.Finish()
|
||||
m.writeMessage(builder, message, baseStyle, width, grouped)
|
||||
text := builder.Finish()
|
||||
// Without a header or content, the first line is the empty one that attachments and embeds start below.
|
||||
if len(text) > 1 && len(text[0]) == 0 {
|
||||
text = text[1:]
|
||||
}
|
||||
return text
|
||||
}
|
||||
|
||||
// rebuildItems replaces date separators while retaining message views and selection.
|
||||
// grouped reports whether message is drawn under the header of previous, the message before it.
|
||||
func (m Model) grouped(previous, message discord.Message) bool {
|
||||
cfg := m.cfg.UI.MessagesList.Grouping
|
||||
return cfg.Enabled &&
|
||||
// Replies and system messages start a group, as their header says more than who wrote them.
|
||||
message.Type == discord.DefaultMessage &&
|
||||
(previous.Type == discord.DefaultMessage || previous.Type == discord.InlinedReplyMessage) &&
|
||||
// Webhooks post under one ID with a name of each message's own.
|
||||
message.Author.ID == previous.Author.ID && message.Author.Username == previous.Author.Username &&
|
||||
message.Timestamp.Time().Sub(previous.Timestamp.Time()) <= cfg.Interval
|
||||
}
|
||||
|
||||
// rebuildItems replaces date separators and groups while retaining message views and selection.
|
||||
func (m *Model) rebuildItems() {
|
||||
items := make([]messageItem, 0, len(m.items))
|
||||
cursor, target := -1, -1
|
||||
var previous discord.Timestamp
|
||||
var previous discord.Message
|
||||
for i, item := range m.items {
|
||||
if item.separator {
|
||||
continue
|
||||
}
|
||||
if m.cfg.DateSeparator.Enabled && (len(items) == 0 || !sameLocalDate(previous, item.message.Timestamp)) {
|
||||
separator := m.cfg.DateSeparator.Enabled && (len(items) == 0 || !sameLocalDate(previous.Timestamp, item.message.Timestamp))
|
||||
if separator {
|
||||
items = append(items, messageItem{separator: true})
|
||||
}
|
||||
// A message that joins or leaves a group is drawn again, with or without its header.
|
||||
if grouped := len(items) > 0 && !separator && m.grouped(previous, item.message); grouped != item.grouped {
|
||||
item.grouped, item.text = grouped, nil
|
||||
}
|
||||
if i == m.cursor {
|
||||
cursor = len(items)
|
||||
}
|
||||
|
|
@ -376,7 +416,7 @@ func (m *Model) rebuildItems() {
|
|||
target = len(items)
|
||||
}
|
||||
items = append(items, item)
|
||||
previous = item.message.Timestamp
|
||||
previous = item.message
|
||||
}
|
||||
m.items = items
|
||||
// The selected and target messages only move with the separators, so they are not scrolled to.
|
||||
|
|
@ -410,7 +450,7 @@ func (m *Model) onRowCursorChanged(index int) {
|
|||
m.setCursor(target)
|
||||
}
|
||||
|
||||
func (m Model) writeMessage(builder *richtext.Builder, message discord.Message, baseStyle tview.Style, width int) {
|
||||
func (m Model) writeMessage(builder *richtext.Builder, message discord.Message, baseStyle tview.Style, width int, grouped bool) {
|
||||
if m.blocked(message.Author) {
|
||||
builder.Write("Blocked message", baseStyle.Foreground(color.Red).Bold(true))
|
||||
return
|
||||
|
|
@ -419,18 +459,18 @@ func (m Model) writeMessage(builder *richtext.Builder, message discord.Message,
|
|||
switch message.Type {
|
||||
case discord.DefaultMessage:
|
||||
if message.Reference != nil && message.Reference.Type == discord.MessageReferenceTypeForward {
|
||||
m.drawForwardedMessage(builder, message, baseStyle)
|
||||
m.drawForwardedMessage(builder, message, baseStyle, grouped)
|
||||
} else {
|
||||
m.drawDefaultMessage(builder, message, baseStyle, width)
|
||||
m.drawDefaultMessage(builder, message, baseStyle, width, grouped)
|
||||
}
|
||||
case discord.GuildMemberJoinMessage:
|
||||
m.drawLayout(builder, message, baseStyle, func() { builder.Write("joined the server.", baseStyle) })
|
||||
m.drawLayout(builder, message, baseStyle, false, func() { builder.Write("joined the server.", baseStyle) })
|
||||
case discord.InlinedReplyMessage:
|
||||
m.drawDefaultMessage(builder, message, baseStyle, width)
|
||||
m.drawDefaultMessage(builder, message, baseStyle, width, false)
|
||||
case discord.ChannelPinnedMessage:
|
||||
m.drawPinnedMessage(builder, message, baseStyle)
|
||||
default:
|
||||
m.drawLayout(builder, message, baseStyle, nil)
|
||||
m.drawLayout(builder, message, baseStyle, false, nil)
|
||||
}
|
||||
m.drawReactions(builder, message.Reactions, baseStyle)
|
||||
}
|
||||
|
|
@ -464,24 +504,29 @@ func (m Model) formatTimestamp(ts discord.Timestamp) string {
|
|||
|
||||
const editedIndicator = " (edited)"
|
||||
|
||||
func (m Model) drawLayout(builder *richtext.Builder, message discord.Message, baseStyle tview.Style, content func()) {
|
||||
// drawLayout draws the header of message, unless it is grouped, and its content.
|
||||
func (m Model) drawLayout(builder *richtext.Builder, message discord.Message, baseStyle tview.Style, grouped bool, content func()) {
|
||||
if m.cfg.UI.MessagesList.Compact {
|
||||
m.drawCompact(builder, message, baseStyle, content)
|
||||
m.drawCompact(builder, message, baseStyle, grouped, content)
|
||||
return
|
||||
}
|
||||
|
||||
m.drawCozy(builder, message, baseStyle, content)
|
||||
m.drawCozy(builder, message, baseStyle, grouped, content)
|
||||
}
|
||||
|
||||
func (m Model) drawCompact(builder *richtext.Builder, message discord.Message, baseStyle tview.Style, content func()) {
|
||||
func (m Model) drawCompact(builder *richtext.Builder, message discord.Message, baseStyle tview.Style, grouped bool, content func()) {
|
||||
dimStyle := baseStyle.Dim(true)
|
||||
if m.cfg.Timestamps.Enabled {
|
||||
builder.Write(m.formatTimestamp(message.Timestamp)+" ", dimStyle)
|
||||
}
|
||||
|
||||
if !grouped {
|
||||
m.drawAuthor(builder, message, baseStyle)
|
||||
if content != nil {
|
||||
builder.Write(" ", baseStyle)
|
||||
}
|
||||
}
|
||||
if content != nil {
|
||||
content()
|
||||
}
|
||||
|
||||
|
|
@ -490,8 +535,18 @@ func (m Model) drawCompact(builder *richtext.Builder, message discord.Message, b
|
|||
}
|
||||
}
|
||||
|
||||
func (m Model) drawCozy(builder *richtext.Builder, message discord.Message, baseStyle tview.Style, content func()) {
|
||||
func (m Model) drawCozy(builder *richtext.Builder, message discord.Message, baseStyle tview.Style, grouped bool, content func()) {
|
||||
dimStyle := baseStyle.Dim(true)
|
||||
if grouped {
|
||||
if content != nil {
|
||||
content()
|
||||
}
|
||||
if message.EditedTimestamp.IsValid() {
|
||||
builder.Write(editedIndicator, dimStyle)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
m.drawAuthor(builder, message, baseStyle)
|
||||
if m.cfg.Timestamps.Enabled {
|
||||
builder.Write(" "+m.formatTimestamp(message.Timestamp), dimStyle)
|
||||
|
|
@ -607,7 +662,7 @@ func (m Model) drawSnapshotContent(builder *richtext.Builder, parent discord.Mes
|
|||
m.drawContent(builder, message, baseStyle)
|
||||
}
|
||||
|
||||
func (m Model) drawDefaultMessage(builder *richtext.Builder, message discord.Message, baseStyle tview.Style, width int) {
|
||||
func (m Model) drawDefaultMessage(builder *richtext.Builder, message discord.Message, baseStyle tview.Style, width int, grouped bool) {
|
||||
var contentRoot ast.Node
|
||||
var contentSource []byte
|
||||
content := func() { contentRoot, contentSource = m.drawContent(builder, message, baseStyle) }
|
||||
|
|
@ -615,7 +670,7 @@ func (m Model) drawDefaultMessage(builder *richtext.Builder, message discord.Mes
|
|||
content = nil
|
||||
}
|
||||
|
||||
m.drawLayout(builder, message, baseStyle, content)
|
||||
m.drawLayout(builder, message, baseStyle, grouped, content)
|
||||
m.drawEmbeds(builder, message, baseStyle, width, contentRoot, contentSource)
|
||||
|
||||
attachmentStyle := tview.MergeStyle(baseStyle, m.cfg.Theme.MessagesList.AttachmentStyle.Style)
|
||||
|
|
@ -930,9 +985,9 @@ func isMarkdownEscapable(c byte) bool {
|
|||
}
|
||||
}
|
||||
|
||||
func (m Model) drawForwardedMessage(builder *richtext.Builder, message discord.Message, baseStyle tview.Style) {
|
||||
func (m Model) drawForwardedMessage(builder *richtext.Builder, message discord.Message, baseStyle tview.Style, grouped bool) {
|
||||
dimStyle := baseStyle.Dim(true)
|
||||
m.drawLayout(builder, message, baseStyle, func() {
|
||||
m.drawLayout(builder, message, baseStyle, grouped, func() {
|
||||
builder.Write(m.cfg.UI.MessagesList.ForwardedIndicator+" ", dimStyle)
|
||||
m.drawSnapshotContent(builder, message, message.MessageSnapshots[0].Message, baseStyle)
|
||||
builder.Write(" ("+m.formatTimestamp(message.MessageSnapshots[0].Message.Timestamp)+") ", dimStyle)
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import (
|
|||
"slices"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/ayn2op/arikawa/v3/discord"
|
||||
"github.com/ayn2op/arikawa/v3/state"
|
||||
|
|
@ -219,7 +220,7 @@ func TestModelDrawLayout(t *testing.T) {
|
|||
cfg.UI.MessagesList.Compact = compact
|
||||
|
||||
var got strings.Builder
|
||||
for _, line := range ml.renderMessage(message, tview.Style{}, 80) {
|
||||
for _, line := range ml.renderMessage(message, tview.Style{}, 80, false) {
|
||||
for _, segment := range line {
|
||||
got.WriteString(segment.Text)
|
||||
}
|
||||
|
|
@ -233,6 +234,59 @@ func TestModelDrawLayout(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestModelGrouped(t *testing.T) {
|
||||
start := time.Date(2026, 9, 1, 12, 0, 0, 0, time.UTC)
|
||||
message := func(author string, after time.Duration, typ discord.MessageType) discord.Message {
|
||||
return discord.Message{Type: typ, Author: discord.User{ID: 2, Username: author}, Timestamp: discord.NewTimestamp(start.Add(after)), Content: "hi"}
|
||||
}
|
||||
previous := message("a", 0, discord.DefaultMessage)
|
||||
for _, tt := range []struct {
|
||||
name string
|
||||
previous discord.Message
|
||||
message discord.Message
|
||||
want bool
|
||||
}{
|
||||
{"same author", previous, message("a", 10*time.Minute, discord.DefaultMessage), true},
|
||||
{"after a reply", message("a", 0, discord.InlinedReplyMessage), message("a", time.Minute, discord.DefaultMessage), true},
|
||||
{"over the interval", previous, message("a", 10*time.Minute+time.Second, discord.DefaultMessage), false},
|
||||
{"renamed webhook", previous, message("b", time.Minute, discord.DefaultMessage), false},
|
||||
{"reply", previous, message("a", time.Minute, discord.InlinedReplyMessage), false},
|
||||
{"after a system message", message("a", 0, discord.ChannelPinnedMessage), message("a", time.Minute, discord.DefaultMessage), false},
|
||||
} {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := newTestModel(t, testConfig(t)).grouped(tt.previous, tt.message); got != tt.want {
|
||||
t.Fatalf("grouped = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
cfg := testConfig(t)
|
||||
cfg.Timestamps.Enabled = false
|
||||
ml := newTestModel(t, cfg)
|
||||
if cfg.UI.MessagesList.Grouping.Enabled = false; ml.grouped(previous, previous) {
|
||||
t.Fatal("grouped while disabled")
|
||||
}
|
||||
cfg.UI.MessagesList.Grouping.Enabled = true
|
||||
// Newest first, as Discord sends them.
|
||||
ml.setMessages([]discord.Message{message("a", 2*time.Minute, discord.DefaultMessage), message("a", time.Minute, discord.DefaultMessage), previous})
|
||||
newest := ml.messageIndex(len(ml.items), -1)
|
||||
if !ml.items[newest].grouped || ml.items[ml.messageIndex(-1, 1)].grouped {
|
||||
t.Fatal("only the first message has a header")
|
||||
}
|
||||
if got := ml.renderMessage(ml.items[newest].message, tview.Style{}, 80, true); len(got) != 1 || got[0][0].Text != "hi" {
|
||||
t.Fatalf("grouped message = %v, want only its content", got)
|
||||
}
|
||||
second := ml.messageIndex(newest, -1)
|
||||
ml.buildItem(second)
|
||||
ml.deleteMessage(ml.messageIndex(-1, 1))
|
||||
if first := ml.messageIndex(-1, 1); ml.items[first].grouped || ml.items[first].text != nil {
|
||||
t.Fatal("deleting the first message did not give the next its header")
|
||||
}
|
||||
if ml.AddMessage(message("a", 3*time.Minute, discord.DefaultMessage)); !ml.items[len(ml.items)-1].grouped {
|
||||
t.Fatal("added message was not grouped")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMessageViewLayout(t *testing.T) {
|
||||
cfg := testConfig(t)
|
||||
cfg.UI.MessagesList.Compact = true
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue