|
|
@@ -211,10 +211,10 @@ func (gt *guildsTree) createChannelNode(node *tview.TreeNode, channel discord.Ch
|
|
|
channelNode.SetIndent(gt.cfg.Theme.GuildsTree.Indents.GroupDM)
|
|
|
case discord.GuildCategory:
|
|
|
channelNode.SetIndent(gt.cfg.Theme.GuildsTree.Indents.Category)
|
|
|
- channelNode.SetExpandable(true).SetExpanded(true)
|
|
|
+ channelNode.SetExpandable(true).SetExpanded(gt.guildState.isChannelExpanded(channel.ID, true))
|
|
|
case discord.GuildForum:
|
|
|
channelNode.SetIndent(gt.cfg.Theme.GuildsTree.Indents.Forum)
|
|
|
- channelNode.SetExpandable(true).SetExpanded(false)
|
|
|
+ channelNode.SetExpandable(true).SetExpanded(gt.guildState.isChannelExpanded(channel.ID, false))
|
|
|
default:
|
|
|
channelNode.SetIndent(gt.cfg.Theme.GuildsTree.Indents.Channel)
|
|
|
}
|
|
|
@@ -271,12 +271,19 @@ func (gt *guildsTree) createChannelNodes(node *tview.TreeNode, channels []discor
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+func (gt *guildsTree) persistExpandState(ref any, expanded bool) {
|
|
|
+ switch ref := ref.(type) {
|
|
|
+ case discord.GuildID:
|
|
|
+ go gt.guildState.setExpanded(ref, expanded)
|
|
|
+ case discord.ChannelID:
|
|
|
+ go gt.guildState.setChannelExpanded(ref, expanded)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
func (gt *guildsTree) onSelected(node *tview.TreeNode) tview.Command {
|
|
|
if len(node.GetChildren()) != 0 {
|
|
|
node.SetExpanded(!node.IsExpanded())
|
|
|
- if guildID, ok := node.GetReference().(discord.GuildID); ok {
|
|
|
- go gt.guildState.setExpanded(guildID, node.IsExpanded())
|
|
|
- }
|
|
|
+ gt.persistExpandState(node.GetReference(), node.IsExpanded())
|
|
|
return nil
|
|
|
}
|
|
|
|
|
|
@@ -356,7 +363,18 @@ func (gt *guildsTree) loadChannel(channel discord.Channel) tview.Command {
|
|
|
return nil
|
|
|
}
|
|
|
|
|
|
- go gt.chat.state.ReadState.MarkRead(channel.ID, channel.LastMessageID)
|
|
|
+ // Use the newest fetched message ID for MarkRead — channel.LastMessageID
|
|
|
+ // can be stale in the cabinet cache, causing some channels to remain unread.
|
|
|
+ // Messages are snowflake-ordered, so the last element is the newest.
|
|
|
+ lastMsgID := channel.LastMessageID
|
|
|
+ if len(messages) > 0 {
|
|
|
+ if newest := messages[len(messages)-1].ID; newest > lastMsgID {
|
|
|
+ lastMsgID = newest
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if lastMsgID.IsValid() {
|
|
|
+ go gt.chat.state.ReadState.MarkRead(channel.ID, lastMsgID)
|
|
|
+ }
|
|
|
|
|
|
if guildID := channel.GuildID; guildID.IsValid() {
|
|
|
gt.chat.messagesList.requestGuildMembers(guildID, messages)
|
|
|
@@ -376,9 +394,7 @@ func (gt *guildsTree) collapseParentNode(node *tview.TreeNode) {
|
|
|
return
|
|
|
}
|
|
|
parent.Collapse()
|
|
|
- if guildID, ok := parent.GetReference().(discord.GuildID); ok {
|
|
|
- go gt.guildState.setExpanded(guildID, false)
|
|
|
- }
|
|
|
+ gt.persistExpandState(parent.GetReference(), false)
|
|
|
gt.SetCurrentNode(parent)
|
|
|
}
|
|
|
|
|
|
@@ -409,6 +425,14 @@ func (gt *guildsTree) HandleEvent(event tview.Event) tview.Command {
|
|
|
return handler(tcell.NewEventKey(tcell.KeyEnter, "", tcell.ModNone))
|
|
|
case keybind.Matches(event, gt.cfg.Keybinds.GuildsTree.YankID.Keybind):
|
|
|
return gt.yankID()
|
|
|
+ case event.Key() == tcell.KeyEsc:
|
|
|
+ // ESC cycles: input → messages → guilds → input
|
|
|
+ if cmd := gt.chat.focusMessageInput(); cmd != nil {
|
|
|
+ return cmd
|
|
|
+ }
|
|
|
+ return gt.chat.focusMessagesList()
|
|
|
+ case event.Key() == tcell.KeyRight:
|
|
|
+ return gt.chat.focusMessagesList()
|
|
|
}
|
|
|
// Do not fall through to TreeView defaults for unmatched keys.
|
|
|
return nil
|