feat(chat): edit latest own message with configurable Up keybind (#837)
This commit is contained in:
parent
bdea99d4f9
commit
8390e9773a
4 changed files with 106 additions and 0 deletions
|
|
@ -180,6 +180,9 @@ yank_id = "i"
|
|||
|
||||
# Only while typing a message
|
||||
[keybinds.composer]
|
||||
# Edit your newest editable message loaded in the current channel, with no age limit.
|
||||
# Only when the composer is empty, with no reply, attachments, or edit in progress.
|
||||
edit_last = "up"
|
||||
# Paste from clipboard (supports both text and images)
|
||||
paste = "ctrl+v"
|
||||
send = "enter"
|
||||
|
|
|
|||
|
|
@ -92,6 +92,7 @@ type MessagesListKeybinds struct {
|
|||
}
|
||||
|
||||
type ComposerKeybinds struct {
|
||||
EditLast Keybind `toml:"edit_last"`
|
||||
Paste Keybind `toml:"paste"`
|
||||
Send Keybind `toml:"send"`
|
||||
Newline Keybind `toml:"newline"`
|
||||
|
|
@ -186,6 +187,7 @@ func defaultMessagesListKeybinds() MessagesListKeybinds {
|
|||
|
||||
func defaultComposerKeybinds() ComposerKeybinds {
|
||||
return ComposerKeybinds{
|
||||
EditLast: desc("edit last"),
|
||||
Paste: desc("paste"),
|
||||
Send: desc("send"),
|
||||
Newline: desc("nl"),
|
||||
|
|
|
|||
|
|
@ -179,6 +179,8 @@ func (c *composer) Update(msg tview.Msg) tview.Cmd {
|
|||
|
||||
case tview.KeyMsg:
|
||||
switch {
|
||||
case keybind.Matches(msg, c.cfg.Keybinds.Composer.EditLast.Keybind) && c.canEditLastMessage():
|
||||
return c.editLastMessage()
|
||||
case keybind.Matches(msg, c.cfg.Keybinds.Composer.Paste.Keybind):
|
||||
return tview.Sequence(pasteImage(), c.forwardToTextArea(tcell.NewEventKey(tcell.KeyCtrlV, "", tcell.ModNone)))
|
||||
case keybind.Matches(msg, c.cfg.Keybinds.Composer.Newline.Keybind):
|
||||
|
|
@ -230,6 +232,31 @@ func (c *composer) Update(msg tview.Msg) tview.Cmd {
|
|||
return c.TextArea.Update(msg)
|
||||
}
|
||||
|
||||
func (c *composer) canEditLastMessage() bool {
|
||||
return !c.Disabled() && !c.edit && c.Text() == "" &&
|
||||
c.sendMessageData.Reference == nil && len(c.sendMessageData.Files) == 0 &&
|
||||
!c.chat.GetVisible(mentionsListLayerName)
|
||||
}
|
||||
|
||||
func (c *composer) editLastMessage() tview.Cmd {
|
||||
channel, ok := c.chat.SelectedChannel()
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
ml := c.chat.messagesList
|
||||
for i := len(ml.messages) - 1; i >= 0; i-- {
|
||||
message := &ml.messages[i]
|
||||
if message.ChannelID != channel.ID || !c.chat.isMe(message.Author.ID) ||
|
||||
!message.ID.IsValid() || message.Content == "" || len(message.MessageSnapshots) > 0 ||
|
||||
(message.Type != discord.DefaultMessage && message.Type != discord.InlinedReplyMessage) {
|
||||
continue
|
||||
}
|
||||
ml.SetCursor(i)
|
||||
return ml.editSelectedMessage()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *composer) toggleReplyMention() {
|
||||
data := c.sendMessageData
|
||||
if data.Reference == nil || data.AllowedMentions == nil || data.AllowedMentions.RepliedUser == nil {
|
||||
|
|
@ -847,6 +874,9 @@ func (c *composer) ShortHelp() []keybind.Keybind {
|
|||
|
||||
cfg := c.cfg.Keybinds.Composer
|
||||
short := []keybind.Keybind{cfg.Send.Keybind, cfg.Newline.Keybind, cfg.Cancel.Keybind, cfg.Paste.Keybind, cfg.OpenEditor.Keybind}
|
||||
if c.canEditLastMessage() {
|
||||
short = append(short, cfg.EditLast.Keybind)
|
||||
}
|
||||
if c.sendMessageData.Reference != nil {
|
||||
short = append(short, cfg.ToggleReplyMention.Keybind)
|
||||
}
|
||||
|
|
@ -874,6 +904,7 @@ func (c *composer) FullHelp() [][]keybind.Keybind {
|
|||
}
|
||||
|
||||
compose := []keybind.Keybind{cfg.Send.Keybind, cfg.Newline.Keybind, cfg.Cancel.Keybind, cfg.Undo.Keybind}
|
||||
compose = append(compose, cfg.EditLast.Keybind)
|
||||
if c.sendMessageData.Reference != nil {
|
||||
compose = append(compose, cfg.ToggleReplyMention.Keybind)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,13 +3,83 @@ package chat
|
|||
import (
|
||||
"maps"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"testing"
|
||||
|
||||
"github.com/ayn2op/arikawa/v3/discord"
|
||||
"github.com/ayn2op/arikawa/v3/gateway"
|
||||
"github.com/ayn2op/arikawa/v3/utils/sendpart"
|
||||
"github.com/ayn2op/discordo/internal/config"
|
||||
"github.com/gdamore/tcell/v3"
|
||||
)
|
||||
|
||||
func TestComposerEditLastMessage(t *testing.T) {
|
||||
for _, scenario := range []string{"latest", "old", "draft", "multiline", "reply", "attachment", "editing", "disabled", "no own message", "remapped", "unbound"} {
|
||||
t.Run(scenario, func(t *testing.T) {
|
||||
cfg, err := config.Load(filepath.Join(t.TempDir(), "config.toml"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
m := NewModel(cfg, "")
|
||||
if err := m.state.Cabinet.MyselfSet(discord.User{ID: 1}, false); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
m.SetSelectedChannel(&discord.Channel{ID: 1})
|
||||
c := m.composer
|
||||
c.SetDisabled(false)
|
||||
messages := []discord.Message{
|
||||
{ID: 1, ChannelID: 1, Author: discord.User{ID: 1}, Content: "old message"},
|
||||
{ID: 2, ChannelID: 1, Author: discord.User{ID: 1}, Content: "latest message"},
|
||||
{ID: 3, ChannelID: 1, Author: discord.User{ID: 2}, Content: "someone else"},
|
||||
{ID: 4, ChannelID: 1, Author: discord.User{ID: 1}, Type: discord.ChannelPinnedMessage, Content: "system"},
|
||||
{ID: 5, ChannelID: 1, Author: discord.User{ID: 1}},
|
||||
}
|
||||
want := ""
|
||||
key := tcell.NewEventKey(tcell.KeyUp, "", tcell.ModNone)
|
||||
switch scenario {
|
||||
case "latest":
|
||||
want = "latest message"
|
||||
case "old":
|
||||
messages = messages[:1]
|
||||
want = "old message"
|
||||
case "draft":
|
||||
c.SetText("draft", true)
|
||||
case "multiline":
|
||||
c.SetText("first line\nsecond line", true)
|
||||
case "reply":
|
||||
c.sendMessageData.Reference = &discord.MessageReference{MessageID: 3}
|
||||
case "attachment":
|
||||
c.sendMessageData.Files = []sendpart.File{{Name: "file"}}
|
||||
case "editing":
|
||||
c.edit = true
|
||||
case "disabled":
|
||||
c.SetDisabled(true)
|
||||
case "no own message":
|
||||
messages = messages[2:3]
|
||||
case "remapped":
|
||||
cfg.Keybinds.Composer.EditLast.SetKeys("ctrl+p")
|
||||
key = tcell.NewEventKey(tcell.KeyCtrlP, "", tcell.ModNone)
|
||||
want = "latest message"
|
||||
case "unbound":
|
||||
cfg.Keybinds.Composer.EditLast.SetKeys()
|
||||
}
|
||||
slices.Reverse(messages)
|
||||
m.messagesList.setMessages(messages)
|
||||
before := c.Text()
|
||||
wasEditing := c.edit
|
||||
c.Update(key)
|
||||
if want != "" {
|
||||
selected, ok := m.messagesList.selectedMessage()
|
||||
if !c.edit || c.Text() != want || !ok || selected.Content != want {
|
||||
t.Fatalf("edit = %v, text = %q, selected = %v", c.edit, c.Text(), selected)
|
||||
}
|
||||
} else if c.Text() != before || c.edit != wasEditing {
|
||||
t.Fatalf("composer changed: edit = %v, text = %q", c.edit, c.Text())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMemberSearchCache(t *testing.T) {
|
||||
cfg, err := config.Load(filepath.Join(t.TempDir(), "config.toml"))
|
||||
if err != nil {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue