diff options
| author | Kyren223 <Kyren223@proton.me> | 2025-01-16 19:11:15 +0200 |
|---|---|---|
| committer | Kyren223 <Kyren223@proton.me> | 2025-01-16 19:11:15 +0200 |
| commit | 15067a81c5ee1ddc1fc6c4760ca13cd8e64c08f7 (patch) | |
| tree | a04ccb749be2bdb22ffa365cfeb18df47696fae7 | |
| parent | 9ebd017160ceac890b8e03b213064ae00c9fc158 (diff) | |
Added client and server side edit message functionality
| -rw-r--r-- | internal/client/gateway/gateway.go | 2 | ||||
| -rw-r--r-- | internal/client/ui/core/chat/chat.go | 77 | ||||
| -rw-r--r-- | internal/client/ui/viminput/viminput.go | 12 | ||||
| -rw-r--r-- | internal/server/api/api.go | 68 | ||||
| -rw-r--r-- | internal/server/server.go | 2 |
5 files changed, 157 insertions, 4 deletions
diff --git a/internal/client/gateway/gateway.go b/internal/client/gateway/gateway.go index 16a3176..4d7c9ed 100644 --- a/internal/client/gateway/gateway.go +++ b/internal/client/gateway/gateway.go @@ -229,6 +229,8 @@ func Send(request packet.Payload) tea.Cmd { err := send(request) if err != nil { log.Println("request send error:", err) + } else { + log.Println("request sent successfully:", request) } return RequestSentMsg{ request: request, diff --git a/internal/client/ui/core/chat/chat.go b/internal/client/ui/core/chat/chat.go index ef9de7e..78109dd 100644 --- a/internal/client/ui/core/chat/chat.go +++ b/internal/client/ui/core/chat/chat.go @@ -26,6 +26,7 @@ var ( focusStyle = lipgloss.NewStyle().Foreground(colors.Focus) readOnlyStyle = lipgloss.NewStyle().Foreground(colors.Gray) mutedStyle = lipgloss.NewStyle().Foreground(colors.Red) + editStyle = lipgloss.NewStyle().Foreground(colors.Gold) ViBlurredBorder = lipgloss.NewStyle(). Border(lipgloss.RoundedBorder(), true, true, false). @@ -33,6 +34,7 @@ var ( ViFocusedBorder = ViBlurredBorder.BorderForeground(colors.Focus) ViReadOnlyBorder = ViBlurredBorder.BorderForeground(colors.Gray) ViMutedBorder = ViBlurredBorder.BorderForeground(colors.Red) + ViEditBorder = ViBlurredBorder.BorderForeground(colors.Gold) VimModeStyle = lipgloss.NewStyle().Bold(true) @@ -84,7 +86,8 @@ type Model struct { base int index int - selectedMessage *snowflake.ID + selectedMessage *data.Message + editingMessage *data.Message messagesHeight int maxMessagesHeight int @@ -107,16 +110,22 @@ func New(width int) Model { vi: vi, focus: false, locked: false, + hasReadAccess: false, + hasWriteAccess: false, networkIndex: -1, receiverIndex: -1, frequencyIndex: -1, base: SnapToBottom, index: Unselected, + selectedMessage: nil, + editingMessage: nil, messagesHeight: 0, maxMessagesHeight: -1, messagesCache: nil, prerender: "", width: width, + style: blurStyle, + borderStyle: ViBlurredBorder, } } @@ -174,6 +183,10 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) { m.borderStyle = ViFocusedBorder m.style = focusStyle m.vi.SetInactive(false) + if m.editingMessage != nil { + m.borderStyle = ViEditBorder + m.style = editStyle + } } else { m.vi.Placeholder = SendMessagePlaceholder m.borderStyle = ViBlurredBorder @@ -193,14 +206,38 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) { if key, ok := msg.(tea.KeyMsg); ok { inNormalQ := key.String() == "q" && m.vi.Mode() == viminput.NormalMode inInsertCtrlQ := key.String() == "ctrl+q" && m.vi.Mode() == viminput.InsertMode + if inNormalQ || inInsertCtrlQ { m.locked = false + + if m.editingMessage != nil { + m.editingMessage = nil + m.borderStyle = ViBlurredBorder + m.style = blurStyle + m.vi.Reset() + m.vi, _ = m.vi.Update(msg) + } + m.Prerender() return m, nil } if key.Type == tea.KeyEnter { - cmd := m.sendMessage() + var cmd tea.Cmd + + if m.editingMessage != nil { + cmd = m.editMessage() + m.locked = false + m.editingMessage = nil + + m.borderStyle = ViBlurredBorder + m.style = blurStyle + m.vi.Reset() + m.vi, _ = m.vi.Update(msg) + } else { + cmd = m.sendMessage() + } + m.Prerender() return m, cmd } @@ -251,9 +288,26 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) { if m.selectedMessage != nil { log.Println("deleting message:", m.selectedMessage) cmd = gateway.Send(&packet.DeleteMessage{ - Message: *m.selectedMessage, + Message: m.selectedMessage.ID, }) } + + case "e": + if m.selectedMessage != nil { + log.Println("editing message:", m.selectedMessage) + m.editingMessage = m.selectedMessage + + m.vi.Reset() + m.vi.SetString(m.selectedMessage.Content) + + m.locked = true + m.vi.SetMode(viminput.InsertMode) + m.vi.SetCursorLine(len(m.vi.Lines()) - 1) + m.vi.SetCursorColumn(len(m.vi.Lines()[m.vi.CursorLine()])) + + m.borderStyle = ViEditBorder + m.style = editStyle + } } } @@ -627,7 +681,7 @@ func (m *Model) renderMessageGroup(group []data.Message, remaining *int, height *remaining-- // For the header if selectedIndex != -1 { - m.selectedMessage = &group[selectedIndex].ID + m.selectedMessage = &group[selectedIndex] if selectedIndex == len(group)-1 { buf = m.renderHeader(group[selectedIndex], true) @@ -752,3 +806,18 @@ func (m *Model) SetIndex(index int) { m.index = Unselected } } + +func (m *Model) editMessage() tea.Cmd { + message := m.vi.String() + if len(message) > MaxCharCount { + return nil + } + if len(strings.TrimSpace(message)) == 0 { + return nil + } + + return gateway.Send(&packet.EditMessage{ + Message: m.editingMessage.ID, + Content: message, + }) +} diff --git a/internal/client/ui/viminput/viminput.go b/internal/client/ui/viminput/viminput.go index 296ce6d..6bc66d1 100644 --- a/internal/client/ui/viminput/viminput.go +++ b/internal/client/ui/viminput/viminput.go @@ -2104,3 +2104,15 @@ func (m *Model) SetInactive(inactive bool) { func (m *Model) Inactive() bool { return m.inactive } + +func (m *Model) Lines() [][]rune { + return m.lines +} + +func (m *Model) CursorLine() int { + return m.cursorLine +} + +func (m *Model) CursorColumn() int { + return m.cursorColumn +} diff --git a/internal/server/api/api.go b/internal/server/api/api.go index f0d1b1f..24112cf 100644 --- a/internal/server/api/api.go +++ b/internal/server/api/api.go @@ -891,3 +891,71 @@ func DeleteMessage(ctx context.Context, sess *session.Session, request *packet.D assert.Never("unreachable") return nil } + +func EditMessage(ctx context.Context, sess *session.Session, request *packet.EditMessage) packet.Payload { + if len(request.Content) > packet.MaxMessageBytes { + return &packet.Error{Error: fmt.Sprintf( + "message conent must not exceed %v bytes", + packet.MaxMessageBytes, + )} + } + + content := strings.TrimSpace(request.Content) + if content == "" { + return &packet.Error{Error: "message content must not be blank"} + } + + queries := data.New(db) + + message, err := queries.GetMessageById(ctx, request.Message) + if err == sql.ErrNoRows { + return &packet.Error{Error: "message doesn't exist"} + } + if err != nil { + log.Println("database error 0:", err) + return &ErrInternalError + } + + if message.FrequencyID != nil { + frequency, err := queries.GetFrequencyById(ctx, *message.FrequencyID) + if err != nil { + log.Println("database error 1:", err) + return &ErrInternalError + } + + // Note: it is possible to edit your messages in any frequency + // regardless if you are in the network or if you have access to + // the frequency, as long as you know the message ID + // This should be fine but may be changed later to be more strict + if message.SenderID != sess.ID() { + return &ErrPermissionDenied + } + + editedMessage, err := queries.EditMessage(ctx, data.EditMessageParams{ + Content: content, + ID: message.ID, + }) + if err != nil { + log.Println("database error 4:", err) + return &ErrInternalError + } + + return NetworkPropagateWithFilter(ctx, sess, frequency.NetworkID, &packet.MessagesInfo{ + Messages: []data.Message{editedMessage}, + RemovedMessages: nil, + }, func(userId snowflake.ID) (pass bool) { + if frequency.Perms != packet.PermNoAccess { + return true + } + isAdmin, _ := IsNetworkAdmin(ctx, queries, userId, frequency.NetworkID) + return isAdmin + }) + } + + if message.ReceiverID != nil { + return &ErrNotImplemented + } + + assert.Never("unreachable") + return nil +} diff --git a/internal/server/server.go b/internal/server/server.go index 95c22f6..8e69d70 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -361,6 +361,8 @@ func processRequest(ctx context.Context, sess *session.Session, request packet.P case *packet.SendMessage: response = timeout(20*time.Millisecond, api.SendMessage, ctx, sess, request) + case *packet.EditMessage: + response = timeout(5*time.Millisecond, api.EditMessage, ctx, sess, request) case *packet.DeleteMessage: response = timeout(5*time.Millisecond, api.DeleteMessage, ctx, sess, request) case *packet.RequestMessages: |
