Delete ActionsHandler

Move the fucntionality of ActionsHandler into ChatbarCache and ActionsModel.

At this stage there wasn't much left that is was doing and the functionality could easily move.
This commit is contained in:
James Graham
2024-11-01 17:00:08 +00:00
parent f5d6f87afe
commit 85d625f6ac
10 changed files with 163 additions and 239 deletions

View File

@@ -53,12 +53,6 @@ ecm_add_test(
TEST_NAME messageeventmodeltest
)
ecm_add_test(
actionshandlertest.cpp
LINK_LIBRARIES neochat Qt::Test
TEST_NAME actionshandlertest
)
ecm_add_test(
windowcontrollertest.cpp
LINK_LIBRARIES neochat Qt::Test

View File

@@ -1,41 +0,0 @@
// SPDX-FileCopyrightText: 2023 James Graham <james.h.graham@protonmail.com>
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
#include <QTest>
#include "actionshandler.h"
#include "chatbarcache.h"
#include "testutils.h"
class ActionsHandlerTest : public QObject
{
Q_OBJECT
private:
Quotient::Connection *connection = Quotient::Connection::makeMockConnection(QStringLiteral("@bob:kde.org"));
private Q_SLOTS:
void nullObject();
};
void ActionsHandlerTest::nullObject()
{
QTest::ignoreMessage(QtWarningMsg, "ActionsHandler::handleMessageEvent - called with m_room and/or chatBarCache set to nullptr.");
ActionsHandler::handleMessageEvent(nullptr, nullptr);
auto chatBarCache = new ChatBarCache(this);
QTest::ignoreMessage(QtWarningMsg, "ActionsHandler::handleMessageEvent - called with m_room and/or chatBarCache set to nullptr.");
ActionsHandler::handleMessageEvent(nullptr, chatBarCache);
auto room = new TestUtils::TestRoom(connection, QStringLiteral("#myroom:kde.org"));
QTest::ignoreMessage(QtWarningMsg, "ActionsHandler::handleMessageEvent - called with m_room and/or chatBarCache set to nullptr.");
ActionsHandler::handleMessageEvent(room, nullptr);
// The final one should throw no warning so we make sure.
QTest::failOnWarning("ActionsHandler::handleMessageEvent - called with m_room and/or chatBarCache set to nullptr.");
ActionsHandler::handleMessageEvent(room, chatBarCache);
}
QTEST_GUILESS_MAIN(ActionsHandlerTest)
#include "actionshandlertest.moc"

View File

@@ -10,8 +10,6 @@ endif()
add_library(neochat STATIC
controller.cpp
controller.h
actionshandler.cpp
actionshandler.h
models/emojimodel.cpp
models/emojimodel.h
emojitones.cpp

View File

@@ -1,144 +0,0 @@
// SPDX-FileCopyrightText: 2020 Carl Schwan <carlschwan@kde.org>
// SPDX-FileCopyrightText: 2024 James Graham <james.h.graham@protonmail.com>
// SPDX-License-Identifier: GPL-3.0-or-later
#include "actionshandler.h"
#include "chatbarcache.h"
#include "models/actionsmodel.h"
#include "neochatconfig.h"
#include "texthandler.h"
using namespace Quotient;
using namespace Qt::StringLiterals;
void ActionsHandler::handleMessageEvent(NeoChatRoom *room, ChatBarCache *chatBarCache)
{
if (room == nullptr || chatBarCache == nullptr) {
qWarning() << "ActionsHandler::handleMessageEvent - called with m_room and/or chatBarCache set to nullptr.";
return;
}
if (!chatBarCache->attachmentPath().isEmpty()) {
QUrl url(chatBarCache->attachmentPath());
auto path = url.isLocalFile() ? url.toLocalFile() : url.toString();
room->uploadFile(QUrl(path), chatBarCache->text().isEmpty() ? path.mid(path.lastIndexOf(u'/') + 1) : chatBarCache->text());
chatBarCache->setAttachmentPath({});
chatBarCache->setText({});
return;
}
const auto handledText = handleMentions(chatBarCache);
const auto result = handleQuickEdit(room, handledText);
if (!result) {
handleMessage(room, handledText, chatBarCache);
}
}
QString ActionsHandler::handleMentions(ChatBarCache *chatBarCache)
{
const auto mentions = chatBarCache->mentions();
std::sort(mentions->begin(), mentions->end(), [](const auto &a, const auto &b) -> bool {
return a.cursor.anchor() > b.cursor.anchor();
});
auto handledText = chatBarCache->text();
for (const auto &mention : *mentions) {
if (mention.text.isEmpty() || mention.id.isEmpty()) {
continue;
}
handledText = handledText.replace(mention.cursor.anchor(),
mention.cursor.position() - mention.cursor.anchor(),
QStringLiteral("[%1](https://matrix.to/#/%2)").arg(mention.text.toHtmlEscaped(), mention.id));
}
mentions->clear();
return handledText;
}
bool ActionsHandler::handleQuickEdit(NeoChatRoom *room, const QString &handledText)
{
if (room == nullptr) {
return false;
}
if (NeoChatConfig::allowQuickEdit()) {
QRegularExpression sed(QStringLiteral("^s/([^/]*)/([^/]*)(/g)?$"));
auto match = sed.match(handledText);
if (match.hasMatch()) {
const QString regex = match.captured(1);
const QString replacement = match.captured(2).toHtmlEscaped();
const QString flags = match.captured(3);
for (auto it = room->messageEvents().crbegin(); it != room->messageEvents().crend(); it++) {
if (const auto event = eventCast<const RoomMessageEvent>(&**it)) {
#if Quotient_VERSION_MINOR > 8
if (event->senderId() == room->localMember().id() && event->has<EventContent::TextContent>()) {
#else
if (event->senderId() == room->localMember().id() && event->hasTextContent()) {
#endif
QString originalString;
if (event->content()) {
#if Quotient_VERSION_MINOR > 8
originalString = event->get<EventContent::TextContent>()->body;
#else
originalString = static_cast<const Quotient::EventContent::TextContent *>(event->content())->body;
#endif
} else {
originalString = event->plainBody();
}
if (flags == "/g"_L1) {
room->postHtmlMessage(handledText, originalString.replace(regex, replacement), event->msgtype(), {}, event->id());
} else {
room->postHtmlMessage(handledText,
originalString.replace(originalString.indexOf(regex), regex.size(), replacement),
event->msgtype(),
{},
event->id());
}
return true;
}
}
}
}
}
return false;
}
void ActionsHandler::handleMessage(NeoChatRoom *room, QString handledText, ChatBarCache *chatBarCache)
{
if (room == nullptr) {
return;
}
auto messageType = RoomMessageEvent::MsgType::Text;
if (handledText.startsWith(QLatin1Char('/'))) {
for (const auto &action : ActionsModel::instance().allActions()) {
if (handledText.indexOf(action.prefix) == 1
&& (handledText.indexOf(" "_ls) == action.prefix.length() + 1 || handledText.length() == action.prefix.length() + 1)) {
handledText = action.handle(handledText.mid(action.prefix.length() + 1).trimmed(), room, chatBarCache);
if (action.messageType.has_value()) {
messageType = *action.messageType;
}
if (action.messageAction) {
break;
} else {
return;
}
}
}
}
TextHandler textHandler;
textHandler.setData(handledText);
handledText = textHandler.handleSendText();
if (handledText.length() == 0) {
return;
}
room->postMessage(chatBarCache->text(), handledText, messageType, chatBarCache->replyId(), chatBarCache->editId(), chatBarCache->threadId());
}
#include "moc_actionshandler.cpp"

View File

@@ -1,43 +0,0 @@
// SPDX-FileCopyrightText: 2020 Carl Schwan <carlschwan@kde.org>
// SPDX-FileCopyrightText: 2024 James Graham <james.h.graham@protonmail.com>
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include <QString>
class ChatBarCache;
class NeoChatRoom;
/**
* @class ActionsHandler
*
* This class contains functions to handle chat messages ready for posting to a room.
*
* Everything that needs to be done to prepare the message for posting in a room
* including:
* - File handling
* - User mentions
* - Quick edits
* - Chat actions
* - Custom emojis
*
* @note A chat action is a message starting with /, resulting in something other
* than a normal message being sent (e.g. /me, /join).
*
* @sa ActionsModel, NeoChatRoom
*/
class ActionsHandler
{
public:
/**
* @brief Pre-process text and send message event.
*/
static void handleMessageEvent(NeoChatRoom *room, ChatBarCache *chatBarCache);
private:
static QString handleMentions(ChatBarCache *chatBarCache);
static bool handleQuickEdit(NeoChatRoom *room, const QString &handledText);
static void handleMessage(NeoChatRoom *room, QString handledText, ChatBarCache *chatBarCache);
};

View File

@@ -405,7 +405,6 @@ QQC2.Control {
repeatTimer.stop();
root.currentRoom.markAllMessagesAsRead();
textField.clear();
_private.chatBarCache.clearRelations();
messageSent();
}

View File

@@ -5,10 +5,11 @@
#include <Quotient/roommember.h>
#include "actionshandler.h"
#include "chatdocumenthandler.h"
#include "eventhandler.h"
#include "models/actionsmodel.h"
#include "neochatroom.h"
#include "texthandler.h"
ChatBarCache::ChatBarCache(QObject *parent)
: QObject(parent)
@@ -29,6 +30,37 @@ void ChatBarCache::setText(const QString &text)
Q_EMIT textChanged();
}
QString ChatBarCache::sendText() const
{
if (!attachmentPath().isEmpty()) {
QUrl url(attachmentPath());
auto path = url.isLocalFile() ? url.toLocalFile() : url.toString();
return text().isEmpty() ? path.mid(path.lastIndexOf(u'/') + 1) : text();
}
return formatMentions();
}
QString ChatBarCache::formatMentions() const
{
auto mentions = m_mentions;
std::sort(mentions.begin(), mentions.end(), [](const auto &a, const auto &b) {
return a.cursor.anchor() > b.cursor.anchor();
});
auto formattedText = text();
for (const auto &mention : mentions) {
if (mention.text.isEmpty() || mention.id.isEmpty()) {
continue;
}
formattedText = formattedText.replace(mention.cursor.anchor(),
mention.cursor.position() - mention.cursor.anchor(),
QStringLiteral("[%1](https://matrix.to/#/%2)").arg(mention.text.toHtmlEscaped(), mention.id));
}
return formattedText;
}
bool ChatBarCache::isReplying() const
{
return m_relationType == Reply && !m_relationId.isEmpty();
@@ -268,7 +300,35 @@ void ChatBarCache::postMessage()
return;
}
ActionsHandler::handleMessageEvent(room, this);
if (!attachmentPath().isEmpty()) {
room->uploadFile(QUrl(attachmentPath()), sendText());
clearCache();
return;
}
const auto result = ActionsModel::handleAction(room, this);
if (!result.first.has_value()) {
return;
}
TextHandler textHandler;
textHandler.setData(*std::get<std::optional<QString>>(result));
const auto sendText = textHandler.handleSendText();
if (sendText.length() == 0) {
return;
}
room->postMessage(text(), sendText, *std::get<std::optional<Quotient::RoomMessageEvent::MsgType>>(result), replyId(), editId(), threadId());
clearCache();
}
void ChatBarCache::clearCache()
{
setText({});
m_mentions.clear();
m_savedText = QString();
clearRelations();
}
#include "moc_chatbarcache.cpp"

View File

@@ -153,6 +153,7 @@ public:
explicit ChatBarCache(QObject *parent = nullptr);
QString text() const;
QString sendText() const;
void setText(const QString &text);
bool isReplying() const;
@@ -215,6 +216,8 @@ Q_SIGNALS:
private:
QString m_text = QString();
QString formatMentions() const;
QString m_relationId = QString();
RelationType m_relationType = RelationType::None;
QString m_threadId = QString();
@@ -223,4 +226,6 @@ private:
QString m_savedText;
QPointer<MessageContentModel> m_relationContentModel;
void clearCache();
};

View File

@@ -5,6 +5,7 @@
#include "chatbarcache.h"
#include "enums/messagetype.h"
#include "neochatconfig.h"
#include "neochatconnection.h"
#include "neochatroom.h"
#include "roommanager.h"
@@ -17,6 +18,7 @@
using Action = ActionsModel::Action;
using namespace Quotient;
using namespace Qt::StringLiterals;
QStringList rainbowColors{"#ff2b00"_ls, "#ff5500"_ls, "#ff8000"_ls, "#ffaa00"_ls, "#ffd500"_ls, "#ffff00"_ls, "#d4ff00"_ls, "#aaff00"_ls, "#80ff00"_ls,
"#55ff00"_ls, "#2bff00"_ls, "#00ff00"_ls, "#00ff2b"_ls, "#00ff55"_ls, "#00ff80"_ls, "#00ffaa"_ls, "#00ffd5"_ls, "#00ffff"_ls,
@@ -574,3 +576,82 @@ QList<Action> &ActionsModel::allActions() const
{
return actions;
}
bool ActionsModel::handleQuickEditAction(NeoChatRoom *room, const QString &messageText)
{
if (room == nullptr) {
return false;
}
if (NeoChatConfig::allowQuickEdit()) {
QRegularExpression sed(QStringLiteral("^s/([^/]*)/([^/]*)(/g)?$"));
auto match = sed.match(messageText);
if (match.hasMatch()) {
const QString regex = match.captured(1);
const QString replacement = match.captured(2).toHtmlEscaped();
const QString flags = match.captured(3);
for (auto it = room->messageEvents().crbegin(); it != room->messageEvents().crend(); it++) {
if (const auto event = eventCast<const RoomMessageEvent>(&**it)) {
#if Quotient_VERSION_MINOR > 8
if (event->senderId() == room->localMember().id() && event->has<EventContent::TextContent>()) {
#else
if (event->senderId() == room->localMember().id() && event->hasTextContent()) {
#endif
QString originalString;
if (event->content()) {
#if Quotient_VERSION_MINOR > 8
originalString = static_cast<const Quotient::EventContent::TextContent *>(event->content().get())->body;
#else
originalString = static_cast<const Quotient::EventContent::TextContent *>(event->content())->body;
#endif
} else {
originalString = event->plainBody();
}
if (flags == "/g"_L1) {
room->postHtmlMessage(messageText, originalString.replace(regex, replacement), event->msgtype(), {}, event->id());
} else {
room->postHtmlMessage(messageText,
originalString.replace(originalString.indexOf(regex), regex.size(), replacement),
event->msgtype(),
{},
event->id());
}
return true;
}
}
}
}
}
return false;
}
std::pair<std::optional<QString>, std::optional<Quotient::RoomMessageEvent::MsgType>> ActionsModel::handleAction(NeoChatRoom *room, ChatBarCache *chatBarCache)
{
auto sendText = chatBarCache->sendText();
const auto edited = handleQuickEditAction(room, sendText);
if (edited) {
return std::make_pair(std::nullopt, std::nullopt);
}
std::optional<Quotient::RoomMessageEvent::MsgType> messageType = std::nullopt;
if (sendText.startsWith(QLatin1Char('/'))) {
for (const auto &action : ActionsModel::instance().allActions()) {
if (sendText.indexOf(action.prefix) == 1
&& (sendText.indexOf(" "_ls) == action.prefix.length() + 1 || sendText.length() == action.prefix.length() + 1)) {
sendText = action.handle(sendText.mid(action.prefix.length() + 1).trimmed(), room, chatBarCache);
if (action.messageType.has_value()) {
messageType = action.messageType;
}
if (action.messageAction) {
break;
} else {
return std::make_pair(std::nullopt, std::nullopt);
}
}
}
}
return std::make_pair(sendText, messageType);
}

View File

@@ -90,6 +90,21 @@ public:
*/
QList<Action> &allActions() const;
/**
* @brief Handle special sed style edit action.
*
* @return True if the message has a sed edit which was actioned. False otherwise.
*/
static bool handleQuickEditAction(NeoChatRoom *room, const QString &messageText);
/**
* @brief Handle any action within the message contained in the given ChatBarCache.
*
* @return A modified or unmodified string that needs to be sent or an empty string if
* the handled action replaces sending a normal message.
*/
static std::pair<std::optional<QString>, std::optional<Quotient::RoomMessageEvent::MsgType>> handleAction(NeoChatRoom *room, ChatBarCache *chatBarCache);
private:
ActionsModel() = default;
};