From 78f7f815cabb4ec87748a213df3fd90a7a6b3b28 Mon Sep 17 00:00:00 2001 From: Srevin Saju Date: Sun, 4 Apr 2021 21:50:30 +0300 Subject: [PATCH] feat: add a quick reply workflow using the Ctrl+Up arrow key neochat now supports a quick reply shortcut, which helps to reply to the last event in a room. --- imports/NeoChat/Component/ChatBox/ChatBar.qml | 5 ++- imports/NeoChat/Component/ChatBox/ChatBox.qml | 4 ++ imports/NeoChat/Page/RoomPage.qml | 6 +++ src/messageeventmodel.cpp | 40 +++++++++++++++++++ src/messageeventmodel.h | 1 + src/neochatroom.cpp | 11 +++++ src/neochatroom.h | 1 + 7 files changed, 67 insertions(+), 1 deletion(-) diff --git a/imports/NeoChat/Component/ChatBox/ChatBar.qml b/imports/NeoChat/Component/ChatBox/ChatBar.qml index d0b8e27d5..db8654b26 100644 --- a/imports/NeoChat/Component/ChatBox/ChatBar.qml +++ b/imports/NeoChat/Component/ChatBox/ChatBar.qml @@ -29,6 +29,7 @@ ToolBar { signal messageSent() signal pasteImageTriggered() signal editLastUserMessage() + signal replyPreviousUserMessage() property alias isCompleting: completionMenu.visible @@ -142,7 +143,9 @@ ToolBar { switchRoomUp(); } else if (event.key === Qt.Key_V && event.modifiers & Qt.ControlModifier) { chatBar.pasteImage(); - } else if (event.key === Qt.Key_Up && !(event.modifiers & Qt.ControlModifier)) { + } else if (event.key === Qt.Key_Up && event.modifiers & Qt.ControlModifier) { + replyPreviousUserMessage(); + } else if (event.key === Qt.Key_Up) { editLastUserMessage(); } } diff --git a/imports/NeoChat/Component/ChatBox/ChatBox.qml b/imports/NeoChat/Component/ChatBox/ChatBox.qml index 9d5d1c6f0..9e9bea492 100644 --- a/imports/NeoChat/Component/ChatBox/ChatBox.qml +++ b/imports/NeoChat/Component/ChatBox/ChatBox.qml @@ -18,6 +18,7 @@ Item { signal fancyEffectsReasonFound(string fancyEffect) signal messageSent() signal editLastUserMessage() + signal replyPreviousUserMessage() Kirigami.Theme.colorSet: Kirigami.Theme.View @@ -157,6 +158,9 @@ Item { onEditLastUserMessage: { root.editLastUserMessage(); } + onReplyPreviousUserMessage: { + root.replyPreviousUserMessage(); + } } function checkForFancyEffectsReason() { diff --git a/imports/NeoChat/Page/RoomPage.qml b/imports/NeoChat/Page/RoomPage.qml index b5310799d..dbec80066 100644 --- a/imports/NeoChat/Page/RoomPage.qml +++ b/imports/NeoChat/Page/RoomPage.qml @@ -657,6 +657,12 @@ Kirigami.ScrollablePage { ChatBoxHelper.edit(targetMessage["body"], targetMessage["body"], targetMessage["event_id"]); } } + onReplyPreviousUserMessage: { + const replyResponse = messageEventModel.getLatestMessageFromIndex(0); + if (replyResponse && replyResponse["event_id"]) { + ChatBoxHelper.replyToMessage(replyResponse["event_id"], replyResponse["event"], replyResponse["sender_id"]); + } + } } background: FancyEffectsContainer { diff --git a/src/messageeventmodel.cpp b/src/messageeventmodel.cpp index 4025e0c69..0f9ee7aff 100644 --- a/src/messageeventmodel.cpp +++ b/src/messageeventmodel.cpp @@ -672,3 +672,43 @@ QVariant MessageEventModel::getLastLocalUserMessageEventId() } return targetMessage; } + +QVariant MessageEventModel::getLatestMessageFromIndex(const int baseline) +{ + QVariantMap replyResponse; + const auto &timelineBottom = m_currentRoom->messageEvents().rbegin() + baseline; + + // set a cap limit of baseline + 35 messages, to prevent loading a lot of messages + // in rooms where the user has not sent many messages + const auto limit = timelineBottom + std::min(baseline + 35, m_currentRoom->timelineSize()); + + for (auto it = timelineBottom; it != limit; ++it) { + auto evt = it->event(); + auto e = eventCast(evt); + + auto content = (*it)->contentJson(); + + if (content.contains("m.relates_to")) { + auto relatedContent = content["m.relates_to"].toObject(); + + if (!relatedContent.contains("m.in_reply_to")) { + // the message has been edited once + // so we have to return the id of the related' message instead + replyResponse.insert("event_id", relatedContent["event_id"].toString()); + replyResponse.insert("event", content["m.formatted_body"].toString()); + replyResponse.insert("sender_id", QVariant::fromValue(m_currentRoom->getUser((*it)->senderId()))); + replyResponse.insert("at", -it->index()); + return replyResponse; + } + } + + if (e->msgtype() != MessageEventType::Unknown) { + replyResponse.insert("event_id", (*it)->id()); + replyResponse.insert("event", content["body"].toString()); + replyResponse.insert("sender_id", QVariant::fromValue(m_currentRoom->getUser((*it)->senderId()))); + replyResponse.insert("at", -it->index()); + return replyResponse; + } + } + return replyResponse; +} diff --git a/src/messageeventmodel.h b/src/messageeventmodel.h index f71210b32..1ac16d103 100644 --- a/src/messageeventmodel.h +++ b/src/messageeventmodel.h @@ -70,6 +70,7 @@ public: Q_INVOKABLE [[nodiscard]] int eventIDToIndex(const QString &eventID) const; Q_INVOKABLE [[nodiscard]] QVariant getLastLocalUserMessageEventId(); + Q_INVOKABLE [[nodiscard]] QVariant getLatestMessageFromIndex(const int baseline); private Q_SLOTS: int refreshEvent(const QString &eventId); diff --git a/src/neochatroom.cpp b/src/neochatroom.cpp index d667a1e0a..4816200c0 100644 --- a/src/neochatroom.cpp +++ b/src/neochatroom.cpp @@ -265,6 +265,17 @@ QVariantList NeoChatRoom::getUsers(const QString &keyword) const return matchedList; } +QVariantMap NeoChatRoom::getUser(const QString& userID) const +{ + NeoChatUser user(userID, connection()); + return QVariantMap { + { QStringLiteral("id"), user.id() }, + { QStringLiteral("displayName"), user.displayname(this) }, + { QStringLiteral("avatarMediaId"), user.avatarMediaId(this) }, + { QStringLiteral("color"), user.color() } + }; +} + QUrl NeoChatRoom::urlToMxcUrl(const QUrl &mxcUrl) { return DownloadFileJob::makeRequestUrl(connection()->homeserver(), mxcUrl); diff --git a/src/neochatroom.h b/src/neochatroom.h index 307b7bd88..ece981545 100644 --- a/src/neochatroom.h +++ b/src/neochatroom.h @@ -92,6 +92,7 @@ public: Q_INVOKABLE void saveViewport(int topIndex, int bottomIndex); Q_INVOKABLE [[nodiscard]] QVariantList getUsers(const QString &keyword) const; + Q_INVOKABLE [[nodiscard]] QVariantMap getUser(const QString &userID) const; Q_INVOKABLE QUrl urlToMxcUrl(const QUrl &mxcUrl);