mirror of
https://github.com/uowuo/abaddon.git
synced 2024-11-10 06:00:10 +00:00
Merge pull request #192 from uowuo/message-editing
Improve message editing
This commit is contained in:
commit
49bbc926e7
@ -114,6 +114,10 @@
|
||||
border: 1px solid #026FB9;
|
||||
}
|
||||
|
||||
.message-input.editing {
|
||||
border: 1px solid #b9026f;
|
||||
}
|
||||
|
||||
.message-input.bad-input {
|
||||
border: 1px solid #dd3300;
|
||||
}
|
||||
|
@ -8,7 +8,6 @@
|
||||
#include "audio/manager.hpp"
|
||||
#include "discord/discord.hpp"
|
||||
#include "dialogs/token.hpp"
|
||||
#include "dialogs/editmessage.hpp"
|
||||
#include "dialogs/confirm.hpp"
|
||||
#include "dialogs/setstatus.hpp"
|
||||
#include "dialogs/friendpicker.hpp"
|
||||
@ -948,19 +947,15 @@ void Abaddon::ActionChatInputSubmit(ChatSubmitParams data) {
|
||||
|
||||
if (!m_discord.HasChannelPermission(m_discord.GetUserData().ID, data.ChannelID, Permission::VIEW_CHANNEL)) return;
|
||||
|
||||
m_discord.SendChatMessage(data, NOOP_CALLBACK);
|
||||
if (data.EditingID.IsValid()) {
|
||||
m_discord.EditMessage(data.ChannelID, data.EditingID, data.Message);
|
||||
} else {
|
||||
m_discord.SendChatMessage(data, NOOP_CALLBACK);
|
||||
}
|
||||
}
|
||||
|
||||
void Abaddon::ActionChatEditMessage(Snowflake channel_id, Snowflake id) {
|
||||
const auto msg = m_discord.GetMessage(id);
|
||||
if (!msg.has_value()) return;
|
||||
EditMessageDialog dlg(*m_main_window);
|
||||
dlg.SetContent(msg->Content);
|
||||
auto response = dlg.run();
|
||||
if (response == Gtk::RESPONSE_OK) {
|
||||
auto new_content = dlg.GetContent();
|
||||
m_discord.EditMessage(channel_id, id, new_content);
|
||||
}
|
||||
m_main_window->EditMessage(id);
|
||||
}
|
||||
|
||||
void Abaddon::ActionInsertMention(Snowflake id) {
|
||||
@ -1156,6 +1151,7 @@ int main(int argc, char **argv) {
|
||||
#endif
|
||||
|
||||
spdlog::cfg::load_env_levels();
|
||||
auto log_ui = spdlog::stdout_color_mt("ui");
|
||||
auto log_audio = spdlog::stdout_color_mt("audio");
|
||||
auto log_voice = spdlog::stdout_color_mt("voice");
|
||||
auto log_discord = spdlog::stdout_color_mt("discord");
|
||||
|
@ -497,6 +497,10 @@ void ChatInput::InsertText(const Glib::ustring &text) {
|
||||
m_input.Get().InsertText(text);
|
||||
}
|
||||
|
||||
void ChatInput::Clear() {
|
||||
GetBuffer()->set_text("");
|
||||
}
|
||||
|
||||
Glib::RefPtr<Gtk::TextBuffer> ChatInput::GetBuffer() {
|
||||
return m_input.Get().GetBuffer();
|
||||
}
|
||||
@ -565,6 +569,24 @@ void ChatInput::StopReplying() {
|
||||
m_input.Get().get_style_context()->remove_class("replying");
|
||||
}
|
||||
|
||||
void ChatInput::StartEditing(const Message &message) {
|
||||
m_is_editing = true;
|
||||
m_input.Get().grab_focus();
|
||||
m_input.Get().get_style_context()->add_class("editing");
|
||||
GetBuffer()->set_text(message.Content);
|
||||
m_attachments.Clear();
|
||||
m_attachments_revealer.set_reveal_child(false);
|
||||
}
|
||||
|
||||
void ChatInput::StopEditing() {
|
||||
m_is_editing = false;
|
||||
m_input.Get().get_style_context()->remove_class("editing");
|
||||
}
|
||||
|
||||
bool ChatInput::IsEmpty() {
|
||||
return GetBuffer()->get_char_count() == 0;
|
||||
}
|
||||
|
||||
bool ChatInput::AddFileAsImageAttachment(const Glib::RefPtr<Gio::File> &file) {
|
||||
try {
|
||||
const auto read_stream = file->read();
|
||||
@ -577,7 +599,7 @@ bool ChatInput::AddFileAsImageAttachment(const Glib::RefPtr<Gio::File> &file) {
|
||||
}
|
||||
|
||||
bool ChatInput::CanAttachFiles() {
|
||||
return Abaddon::Get().GetDiscordClient().HasSelfChannelPermission(m_active_channel, Permission::ATTACH_FILES | Permission::SEND_MESSAGES);
|
||||
return !m_is_editing && Abaddon::Get().GetDiscordClient().HasSelfChannelPermission(m_active_channel, Permission::ATTACH_FILES | Permission::SEND_MESSAGES);
|
||||
}
|
||||
|
||||
ChatInput::type_signal_submit ChatInput::signal_submit() {
|
||||
|
@ -129,6 +129,7 @@ public:
|
||||
ChatInput();
|
||||
|
||||
void InsertText(const Glib::ustring &text);
|
||||
void Clear();
|
||||
Glib::RefPtr<Gtk::TextBuffer> GetBuffer();
|
||||
bool ProcessKeyPress(GdkEventKey *event);
|
||||
void AddAttachment(const Glib::RefPtr<Gio::File> &file);
|
||||
@ -139,6 +140,11 @@ public:
|
||||
void StartReplying();
|
||||
void StopReplying();
|
||||
|
||||
void StartEditing(const Message &message);
|
||||
void StopEditing();
|
||||
|
||||
bool IsEmpty();
|
||||
|
||||
private:
|
||||
bool AddFileAsImageAttachment(const Glib::RefPtr<Gio::File> &file);
|
||||
bool CanAttachFiles();
|
||||
@ -149,6 +155,8 @@ private:
|
||||
|
||||
Snowflake m_active_channel;
|
||||
|
||||
bool m_is_editing = false;
|
||||
|
||||
public:
|
||||
using type_signal_submit = sigc::signal<bool, ChatSubmitParams>;
|
||||
using type_signal_escape = sigc::signal<void>;
|
||||
|
@ -133,10 +133,9 @@ void ChatList::ProcessNewMessage(const Message &data, bool prepend) {
|
||||
m_menu_delete_message->set_sensitive(false);
|
||||
m_menu_edit_message->set_sensitive(false);
|
||||
} else {
|
||||
const bool can_edit = client.GetUserData().ID == data->Author.ID;
|
||||
const bool can_delete = can_edit || has_manage;
|
||||
const bool can_delete = (client.GetUserData().ID == data->Author.ID) || has_manage;
|
||||
m_menu_delete_message->set_sensitive(can_delete);
|
||||
m_menu_edit_message->set_sensitive(can_edit);
|
||||
m_menu_edit_message->set_sensitive(data->IsEditable());
|
||||
}
|
||||
|
||||
m_menu.popup_at_pointer(reinterpret_cast<GdkEvent *>(ev));
|
||||
@ -253,6 +252,25 @@ void ChatList::ActuallyRemoveMessage(Snowflake id) {
|
||||
RemoveMessageAndHeader(it->second);
|
||||
}
|
||||
|
||||
std::optional<Snowflake> ChatList::GetLastSentEditableMessage() {
|
||||
const auto &discord = Abaddon::Get().GetDiscordClient();
|
||||
const auto self_id = discord.GetUserData().ID;
|
||||
|
||||
std::map<Snowflake, Gtk::Widget *> ordered(m_id_to_widget.begin(), m_id_to_widget.end());
|
||||
|
||||
for (auto it = ordered.crbegin(); it != ordered.crend(); it++) {
|
||||
const auto *widget = dynamic_cast<ChatMessageItemContainer *>(it->second);
|
||||
if (widget == nullptr) continue;
|
||||
const auto msg = discord.GetMessage(widget->ID);
|
||||
if (!msg.has_value()) continue;
|
||||
if (msg->Author.ID != self_id) continue;
|
||||
if (!msg->IsEditable()) continue;
|
||||
return msg->ID;
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
void ChatList::SetupMenu() {
|
||||
m_menu_copy_id = Gtk::manage(new Gtk::MenuItem("Copy ID"));
|
||||
m_menu_copy_id->signal_activate().connect([this] {
|
||||
|
@ -23,6 +23,7 @@ public:
|
||||
void SetSeparateAll(bool separate);
|
||||
void SetUsePinnedMenu(); // i think i need a better way to do menus
|
||||
void ActuallyRemoveMessage(Snowflake id); // perhaps not the best method name
|
||||
std::optional<Snowflake> GetLastSentEditableMessage();
|
||||
|
||||
private:
|
||||
void SetupMenu();
|
||||
|
@ -50,8 +50,8 @@ ChatWindow::ChatWindow() {
|
||||
|
||||
m_input->signal_submit().connect(sigc::mem_fun(*this, &ChatWindow::OnInputSubmit));
|
||||
m_input->signal_escape().connect([this]() {
|
||||
if (m_is_replying)
|
||||
StopReplying();
|
||||
if (m_is_replying) StopReplying();
|
||||
if (m_is_editing) StopEditing();
|
||||
});
|
||||
m_input->signal_key_press_event().connect(sigc::mem_fun(*this, &ChatWindow::OnKeyPressEvent), false);
|
||||
m_input->show();
|
||||
@ -132,8 +132,8 @@ void ChatWindow::SetActiveChannel(Snowflake id) {
|
||||
m_input->SetActiveChannel(id);
|
||||
m_input_indicator->SetActiveChannel(id);
|
||||
m_rate_limit_indicator->SetActiveChannel(id);
|
||||
if (m_is_replying)
|
||||
StopReplying();
|
||||
if (m_is_replying) StopReplying();
|
||||
if (m_is_editing) StopEditing();
|
||||
|
||||
#ifdef WITH_LIBHANDY
|
||||
m_tab_switcher->ReplaceActiveTab(id);
|
||||
@ -274,15 +274,31 @@ bool ChatWindow::OnInputSubmit(ChatSubmitParams data) {
|
||||
|
||||
data.ChannelID = m_active_channel;
|
||||
data.InReplyToID = m_replying_to;
|
||||
data.EditingID = m_editing_id;
|
||||
|
||||
if (m_active_channel.IsValid())
|
||||
m_signal_action_chat_submit.emit(data); // m_replying_to is checked for invalid in the handler
|
||||
if (m_is_replying)
|
||||
StopReplying();
|
||||
|
||||
if (m_is_replying) StopReplying();
|
||||
if (m_is_editing) StopEditing();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ChatWindow::ProcessKeyEvent(GdkEventKey *e) {
|
||||
if (e->type != GDK_KEY_PRESS) return false;
|
||||
if (e->keyval == GDK_KEY_Up && !(e->state & GDK_SHIFT_MASK) && m_input->IsEmpty()) {
|
||||
const auto edit_id = m_chat->GetLastSentEditableMessage();
|
||||
if (edit_id.has_value()) {
|
||||
StartEditing(*edit_id);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ChatWindow::OnKeyPressEvent(GdkEventKey *e) {
|
||||
if (m_completer.ProcessKeyPress(e))
|
||||
return true;
|
||||
@ -290,6 +306,9 @@ bool ChatWindow::OnKeyPressEvent(GdkEventKey *e) {
|
||||
if (m_input->ProcessKeyPress(e))
|
||||
return true;
|
||||
|
||||
if (ProcessKeyEvent(e))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -300,10 +319,11 @@ void ChatWindow::StartReplying(Snowflake message_id) {
|
||||
m_replying_to = message_id;
|
||||
m_is_replying = true;
|
||||
m_input->StartReplying();
|
||||
if (author.has_value())
|
||||
if (author.has_value()) {
|
||||
m_input_indicator->SetCustomMarkup("Replying to " + author->GetUsernameEscapedBold());
|
||||
else
|
||||
} else {
|
||||
m_input_indicator->SetCustomMarkup("Replying...");
|
||||
}
|
||||
}
|
||||
|
||||
void ChatWindow::StopReplying() {
|
||||
@ -313,6 +333,26 @@ void ChatWindow::StopReplying() {
|
||||
m_input_indicator->ClearCustom();
|
||||
}
|
||||
|
||||
void ChatWindow::StartEditing(Snowflake message_id) {
|
||||
const auto message = Abaddon::Get().GetDiscordClient().GetMessage(message_id);
|
||||
if (!message.has_value()) {
|
||||
spdlog::get("ui")->warn("ChatWindow::StartEditing message is nullopt");
|
||||
return;
|
||||
}
|
||||
m_is_editing = true;
|
||||
m_editing_id = message_id;
|
||||
m_input->StartEditing(*message);
|
||||
m_input_indicator->SetCustomMarkup("Editing...");
|
||||
}
|
||||
|
||||
void ChatWindow::StopEditing() {
|
||||
m_is_editing = false;
|
||||
m_editing_id = Snowflake::Invalid;
|
||||
m_input->StopEditing();
|
||||
m_input->Clear();
|
||||
m_input_indicator->ClearCustom();
|
||||
}
|
||||
|
||||
void ChatWindow::OnScrollEdgeOvershot(Gtk::PositionType pos) {
|
||||
if (pos == Gtk::POS_TOP)
|
||||
m_signal_action_chat_load_history.emit(m_active_channel);
|
||||
|
@ -37,6 +37,9 @@ public:
|
||||
void SetTopic(const std::string &text);
|
||||
void AddAttachment(const Glib::RefPtr<Gio::File> &file);
|
||||
|
||||
void StartEditing(Snowflake message_id);
|
||||
void StopEditing();
|
||||
|
||||
#ifdef WITH_LIBHANDY
|
||||
void OpenNewTab(Snowflake id);
|
||||
TabsState GetTabsState();
|
||||
@ -55,10 +58,14 @@ protected:
|
||||
void StartReplying(Snowflake message_id);
|
||||
void StopReplying();
|
||||
|
||||
bool m_is_editing = false;
|
||||
Snowflake m_editing_id;
|
||||
|
||||
Snowflake m_active_channel;
|
||||
|
||||
bool OnInputSubmit(ChatSubmitParams data);
|
||||
|
||||
bool ProcessKeyEvent(GdkEventKey *e);
|
||||
bool OnKeyPressEvent(GdkEventKey *e);
|
||||
void OnScrollEdgeOvershot(Gtk::PositionType pos);
|
||||
|
||||
|
@ -1,45 +0,0 @@
|
||||
#include "editmessage.hpp"
|
||||
|
||||
EditMessageDialog::EditMessageDialog(Gtk::Window &parent)
|
||||
: Gtk::Dialog("Edit Message", parent, true)
|
||||
, m_layout(Gtk::ORIENTATION_VERTICAL)
|
||||
, m_ok("OK")
|
||||
, m_cancel("Cancel")
|
||||
, m_bbox(Gtk::ORIENTATION_HORIZONTAL) {
|
||||
set_default_size(300, 50);
|
||||
get_style_context()->add_class("app-window");
|
||||
get_style_context()->add_class("app-popup");
|
||||
|
||||
m_ok.signal_clicked().connect([&]() {
|
||||
m_content = m_text.get_buffer()->get_text();
|
||||
response(Gtk::RESPONSE_OK);
|
||||
});
|
||||
|
||||
m_cancel.signal_clicked().connect([&]() {
|
||||
response(Gtk::RESPONSE_CANCEL);
|
||||
});
|
||||
|
||||
m_bbox.pack_start(m_ok, Gtk::PACK_SHRINK);
|
||||
m_bbox.pack_start(m_cancel, Gtk::PACK_SHRINK);
|
||||
m_bbox.set_layout(Gtk::BUTTONBOX_END);
|
||||
|
||||
m_text.set_hexpand(true);
|
||||
|
||||
m_scroll.set_hexpand(true);
|
||||
m_scroll.set_vexpand(true);
|
||||
m_scroll.add(m_text);
|
||||
|
||||
m_layout.add(m_scroll);
|
||||
m_layout.add(m_bbox);
|
||||
get_content_area()->add(m_layout);
|
||||
|
||||
show_all_children();
|
||||
}
|
||||
|
||||
Glib::ustring EditMessageDialog::GetContent() {
|
||||
return m_content;
|
||||
}
|
||||
|
||||
void EditMessageDialog::SetContent(const Glib::ustring &str) {
|
||||
m_text.get_buffer()->set_text(str);
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
#pragma once
|
||||
#include <string>
|
||||
|
||||
class EditMessageDialog : public Gtk::Dialog {
|
||||
public:
|
||||
EditMessageDialog(Gtk::Window &parent);
|
||||
Glib::ustring GetContent();
|
||||
void SetContent(const Glib::ustring &str);
|
||||
|
||||
protected:
|
||||
Gtk::Box m_layout;
|
||||
Gtk::Button m_ok;
|
||||
Gtk::Button m_cancel;
|
||||
Gtk::ButtonBox m_bbox;
|
||||
Gtk::ScrolledWindow m_scroll;
|
||||
Gtk::TextView m_text;
|
||||
|
||||
private:
|
||||
Glib::ustring m_content;
|
||||
};
|
@ -20,6 +20,7 @@ struct ChatSubmitParams {
|
||||
bool Silent = false;
|
||||
Snowflake ChannelID;
|
||||
Snowflake InReplyToID;
|
||||
Snowflake EditingID;
|
||||
Glib::ustring Message;
|
||||
std::vector<Attachment> Attachments;
|
||||
};
|
||||
|
@ -265,6 +265,10 @@ bool Message::IsEdited() const {
|
||||
return m_edited;
|
||||
}
|
||||
|
||||
bool Message::IsEditable() const noexcept {
|
||||
return (Abaddon::Get().GetDiscordClient().GetUserData().ID == Author.ID) && !IsDeleted() && !IsPending && (Type == MessageType::DEFAULT || Type == MessageType::INLINE_REPLY);
|
||||
}
|
||||
|
||||
bool Message::DoesMentionEveryoneOrUser(Snowflake id) const noexcept {
|
||||
if (DoesMentionEveryone) return true;
|
||||
return std::any_of(Mentions.begin(), Mentions.end(), [id](const UserData &user) {
|
||||
|
@ -219,11 +219,13 @@ struct Message {
|
||||
|
||||
void SetDeleted();
|
||||
void SetEdited();
|
||||
[[nodiscard]] bool IsDeleted() const;
|
||||
[[nodiscard]] bool IsEdited() const;
|
||||
bool IsDeleted() const;
|
||||
bool IsEdited() const;
|
||||
|
||||
[[nodiscard]] bool DoesMentionEveryoneOrUser(Snowflake id) const noexcept;
|
||||
[[nodiscard]] bool DoesMention(Snowflake id) const noexcept;
|
||||
bool IsEditable() const noexcept;
|
||||
|
||||
bool DoesMentionEveryoneOrUser(Snowflake id) const noexcept;
|
||||
bool DoesMention(Snowflake id) const noexcept;
|
||||
|
||||
private:
|
||||
bool m_deleted = false;
|
||||
|
@ -168,6 +168,10 @@ void MainWindow::ToggleMenuVisibility() {
|
||||
m_menu_bar.set_visible(!m_menu_bar.get_visible());
|
||||
}
|
||||
|
||||
void MainWindow::EditMessage(Snowflake message_id) {
|
||||
m_chat.StartEditing(message_id);
|
||||
}
|
||||
|
||||
#ifdef WITH_LIBHANDY
|
||||
void MainWindow::GoBack() {
|
||||
m_chat.GoBack();
|
||||
|
@ -29,6 +29,7 @@ public:
|
||||
void UpdateChatReactionRemove(Snowflake id, const Glib::ustring ¶m);
|
||||
void UpdateMenus();
|
||||
void ToggleMenuVisibility();
|
||||
void EditMessage(Snowflake message_id);
|
||||
|
||||
#ifdef WITH_LIBHANDY
|
||||
void GoBack();
|
||||
|
Loading…
Reference in New Issue
Block a user