feat(messageslist): add option to show user status on messages (#454)

This commit is contained in:
spearzificial 2026-10-19 14:29:40 +05:30
parent eb4028957d
commit f68958f75c
5 changed files with 50 additions and 0 deletions

View file

@ -367,6 +367,12 @@ The number of empty rows between messages, except those of a group.
Default: `1` Default: `1`
### ui.messages_list.show_user_status
Whether to show the author's presence status dot alongside their name.
Default: `false`
### ui.messages_list.reply_indicator ### ui.messages_list.reply_indicator
Default: `">"` Default: `">"`

View file

@ -174,6 +174,9 @@ type (
// The number of empty rows between messages, except those of a group. // The number of empty rows between messages, except those of a group.
Spacing uint8 `toml:"spacing"` Spacing uint8 `toml:"spacing"`
// Whether to show the author's presence status dot alongside their name.
ShowUserStatus bool `toml:"show_user_status"`
ReplyIndicator string `toml:"reply_indicator"` ReplyIndicator string `toml:"reply_indicator"`
ForwardedIndicator string `toml:"forwarded_indicator"` ForwardedIndicator string `toml:"forwarded_indicator"`

View file

@ -175,7 +175,14 @@ func (m Model) drawCozy(builder *markdown.Builder, message discord.Message, base
} }
} }
const statusMarker = "●"
func (m Model) drawAuthor(builder *markdown.Builder, message discord.Message, baseStyle tview.Style) { func (m Model) drawAuthor(builder *markdown.Builder, message discord.Message, baseStyle tview.Style) {
if m.cfg.UI.MessagesList.ShowUserStatus && !message.WebhookID.IsValid() {
status := m.status(message.GuildID, message.Author.ID)
builder.Write(statusMarker+" ", m.cfg.Theme.MembersTree.StatusStyle(status))
}
name := message.Author.DisplayOrUsername() name := message.Author.DisplayOrUsername()
foreground := tcell.ColorDefault foreground := tcell.ColorDefault
@ -211,6 +218,17 @@ func (m Model) memberForMessage(message discord.Message) *discord.Member {
return member return member
} }
func (m Model) status(guildID discord.GuildID, userID discord.UserID) discord.Status {
if m.state == nil || m.state.Cabinet == nil {
return discord.OfflineStatus
}
presence, err := m.state.Cabinet.Presence(guildID, userID)
if err != nil {
return discord.OfflineStatus
}
return presence.Status
}
// drawContent renders the message body. root is its parsed content, or nil to parse it here. // drawContent renders the message body. root is its parsed content, or nil to parse it here.
func (m Model) drawContent(builder *markdown.Builder, message discord.Message, root ast.Node, baseStyle tview.Style) { func (m Model) drawContent(builder *markdown.Builder, message discord.Message, root ast.Node, baseStyle tview.Style) {
if root == nil && m.cfg.Markdown.Enabled { if root == nil && m.cfg.Markdown.Enabled {

View file

@ -12,6 +12,7 @@ import (
"github.com/ayn2op/arikawa/v3/discord" "github.com/ayn2op/arikawa/v3/discord"
"github.com/ayn2op/arikawa/v3/state" "github.com/ayn2op/arikawa/v3/state"
"github.com/ayn2op/discordo/internal/config" "github.com/ayn2op/discordo/internal/config"
"github.com/ayn2op/discordo/internal/markdown"
"github.com/ayn2op/ningen/v3" "github.com/ayn2op/ningen/v3"
"github.com/ayn2op/tview" "github.com/ayn2op/tview"
tviewimage "github.com/ayn2op/tview/image" tviewimage "github.com/ayn2op/tview/image"
@ -401,3 +402,22 @@ func TestModelReactionsView(t *testing.T) {
} }
}) })
} }
func TestModelShowUserStatus(t *testing.T) {
cfg := testConfig(t)
cfg.UI.MessagesList.ShowUserStatus = true
ml := newTestModel(t, cfg)
user := discord.User{ID: 10, Username: "alice"}
_ = ml.state.PresenceSet(discord.NullGuildID, &discord.Presence{User: user, Status: discord.OnlineStatus}, false)
msg := discord.Message{ID: 1, Author: user, Content: "hello"}
builder := new(markdown.Builder)
ml.drawAuthor(builder, msg, tcell.StyleDefault)
spans := builder.Finish()
if len(spans) == 0 {
t.Fatal("expected spans from drawAuthor")
}
if !strings.HasPrefix(spans[0].Text, statusMarker) {
t.Fatalf("first span text = %q, want statusMarker %q", spans[0].Text, statusMarker)
}
}

View file

@ -110,6 +110,9 @@ func (m *Model) applyEvent(event gateway.Event) tview.Cmd {
case *gateway.MessageUpdateEvent: case *gateway.MessageUpdateEvent:
return m.messagesList.UpdateMessage(event.Message) return m.messagesList.UpdateMessage(event.Message)
case *gateway.PresenceUpdateEvent: case *gateway.PresenceUpdateEvent:
if m.cfg.UI.MessagesList.ShowUserStatus {
m.messagesList.InvalidateRendered()
}
return tview.Batch(m.updatePane(paneGuildsTree, event), m.updatePane(paneMembersTree, event)) return tview.Batch(m.updatePane(paneGuildsTree, event), m.updatePane(paneMembersTree, event))
case *gateway.GuildMemberListUpdateEvent: case *gateway.GuildMemberListUpdateEvent:
return m.updatePane(paneMembersTree, event) return m.updatePane(paneMembersTree, event)