From 7a5de2588599750e4a2ea13ee19442949ac5b301 Mon Sep 17 00:00:00 2001 From: James Graham Date: Wed, 16 Jul 2025 18:27:03 +0100 Subject: [PATCH] Further refactor MessageContentModel Further refactor MessageContentModel. Move away from special casing certian MessageContentTypes. Use forEachComponentOfType more --- src/libneochat/enums/messagecomponenttype.h | 8 ++ src/libneochat/texthandler.cpp | 4 +- .../models/messagecontentmodel.cpp | 109 +++++++----------- .../models/messagecontentmodel.h | 29 ++++- 4 files changed, 75 insertions(+), 75 deletions(-) diff --git a/src/libneochat/enums/messagecomponenttype.h b/src/libneochat/enums/messagecomponenttype.h index ca41c164c..102414ef4 100644 --- a/src/libneochat/enums/messagecomponenttype.h +++ b/src/libneochat/enums/messagecomponenttype.h @@ -78,7 +78,15 @@ public: { using namespace Quotient; + if (event.isRedacted()) { + return MessageComponentType::Text; + } + if (const auto e = eventCast(&event)) { + if (e->rawMsgtype() == u"m.key.verification.request"_s) { + return MessageComponentType::Verification; + } + switch (e->msgtype()) { case MessageEventType::Emote: return MessageComponentType::Text; diff --git a/src/libneochat/texthandler.cpp b/src/libneochat/texthandler.cpp index cc98bf3d5..f0b942edb 100644 --- a/src/libneochat/texthandler.cpp +++ b/src/libneochat/texthandler.cpp @@ -570,8 +570,8 @@ QVariantMap TextHandler::getAttributes(const QString &tag, const QString &tagStr QList TextHandler::textComponents(QString string, Qt::TextFormat inputFormat, const NeoChatRoom *room, const Quotient::RoomEvent *event, bool isEdited) { - if (string.isEmpty()) { - return {}; + if (string.trimmed().isEmpty()) { + return {MessageComponent{MessageComponentType::Text, i18n("This event does not have any content."), {}}}; } // Strip mx-reply if present. diff --git a/src/messagecontent/models/messagecontentmodel.cpp b/src/messagecontent/models/messagecontentmodel.cpp index 0eb5fc7ad..b49a512a7 100644 --- a/src/messagecontent/models/messagecontentmodel.cpp +++ b/src/messagecontent/models/messagecontentmodel.cpp @@ -29,7 +29,6 @@ #include "chatbarcache.h" #include "contentprovider.h" #include "filetype.h" -#include "linkpreviewer.h" #include "models/reactionmodel.h" #include "neochatconnection.h" #include "neochatroom.h" @@ -422,7 +421,8 @@ bool MessageContentModel::hasComponentType(MessageComponentType::Type type) != m_components.cend(); } -void MessageContentModel::forEachComponentOfType(MessageComponentType::Type type, std::function function) +void MessageContentModel::forEachComponentOfType(MessageComponentType::Type type, + std::function function) { auto it = m_components.begin(); while ((it = std::find_if(it, @@ -431,12 +431,12 @@ void MessageContentModel::forEachComponentOfType(MessageComponentType::Type type return component.type == type; })) != m_components.end()) { - function(index(it - m_components.begin())); - ++it; + it = function(it); } } -void MessageContentModel::forEachComponentOfType(QList types, std::function function) +void MessageContentModel::forEachComponentOfType(QList types, + std::function function) { for (const auto &type : types) { forEachComponentOfType(type, function); @@ -466,6 +466,10 @@ void MessageContentModel::resetModel() m_components += messageContentComponents(); endResetModel(); + if (m_room->urlPreviewEnabled()) { + forEachComponentOfType({MessageComponentType::Text, MessageComponentType::Quote}, m_linkPreviewFunction); + } + updateReplyModel(); updateReactionModel(); } @@ -485,6 +489,10 @@ void MessageContentModel::resetContent(bool isEditing, bool isThreading) m_components += newComponents; endInsertRows(); + if (m_room->urlPreviewEnabled()) { + forEachComponentOfType({MessageComponentType::Text, MessageComponentType::Quote}, m_linkPreviewFunction); + } + updateReplyModel(); updateReactionModel(); } @@ -498,27 +506,13 @@ QList MessageContentModel::messageContentComponents(bool isEdi QList newComponents; - const auto roomMessageEvent = eventCast(event.first); - if (roomMessageEvent && roomMessageEvent->rawMsgtype() == u"m.key.verification.request"_s) { - newComponents += MessageComponent{MessageComponentType::Verification, QString(), {}}; - return newComponents; - } - - if (event.first->isRedacted()) { - newComponents += MessageComponent{MessageComponentType::Text, QString(), {}}; - return newComponents; - } - if (isEditing) { newComponents += MessageComponent{MessageComponentType::ChatBar, QString(), {}}; } else { newComponents.append(componentsForType(MessageComponentType::typeForEvent(*event.first, m_isReply))); } - if (m_room->urlPreviewEnabled()) { - newComponents = addLinkPreviews(newComponents); - } - + const auto roomMessageEvent = eventCast(event.first); #if Quotient_VERSION_MINOR > 9 || (Quotient_VERSION_MINOR == 9 && Quotient_VERSION_PATCH > 1) if (m_threadsEnabled && roomMessageEvent && (roomMessageEvent->isThreaded() || m_room->threads().contains(roomMessageEvent->id())) && roomMessageEvent->id() == roomMessageEvent->threadRootEventId()) { @@ -597,25 +591,26 @@ QList MessageContentModel::componentsForType(MessageComponentT } switch (type) { + case MessageComponentType::Verification: { + return {MessageComponent{MessageComponentType::Verification, QString(), {}}}; + } case MessageComponentType::Text: { if (const auto roomMessageEvent = eventCast(event.first)) { - auto body = EventHandler::rawMessageBody(*roomMessageEvent); - if (body.trimmed().isEmpty()) { - return TextHandler().textComponents(i18n("This event does not have any content."), - Qt::TextFormat::RichText, - m_room, - roomMessageEvent, - roomMessageEvent->isReplaced()); - } else { - return TextHandler().textComponents(body, - EventHandler::messageBodyInputFormat(*roomMessageEvent), - m_room, - roomMessageEvent, - roomMessageEvent->isReplaced()); - } + return TextHandler().textComponents(EventHandler::rawMessageBody(*roomMessageEvent), + EventHandler::messageBodyInputFormat(*roomMessageEvent), + m_room, + roomMessageEvent, + roomMessageEvent->isReplaced()); } else { return TextHandler().textComponents(EventHandler::plainBody(m_room, event.first), Qt::TextFormat::PlainText, m_room, event.first, false); } + + const auto roomMessageEvent = eventCast(event.first); + return TextHandler().textComponents(EventHandler::rawMessageBody(*roomMessageEvent), + EventHandler::messageBodyInputFormat(*roomMessageEvent), + m_room, + roomMessageEvent, + roomMessageEvent->isReplaced()); } case MessageComponentType::File: { QList components; @@ -706,42 +701,20 @@ MessageComponent MessageContentModel::linkPreviewComponent(const QUrl &link) } if (linkPreviewer->loaded()) { return MessageComponent{MessageComponentType::LinkPreview, QString(), {{"link"_L1, link}}}; - } else { - connect(linkPreviewer, &LinkPreviewer::loadedChanged, this, [this, link]() { - const auto linkPreviewer = dynamic_cast(m_room->connection())->previewerForLink(link); - if (linkPreviewer != nullptr && linkPreviewer->loaded()) { - for (auto it = m_components.begin(); it != m_components.end(); it++) { - if (it->attributes["link"_L1].toUrl() == link) { - it->type = MessageComponentType::LinkPreview; - Q_EMIT dataChanged(index(it - m_components.begin()), index(it - m_components.begin()), {ComponentTypeRole}); - } + } + connect(linkPreviewer, &LinkPreviewer::loadedChanged, this, [this, link]() { + const auto linkPreviewer = dynamic_cast(m_room->connection())->previewerForLink(link); + if (linkPreviewer != nullptr && linkPreviewer->loaded()) { + forEachComponentOfType(MessageComponentType::LinkPreviewLoad, [this, link](ComponentIt it) { + if (it->attributes["link"_L1].toUrl() == link) { + it->type = MessageComponentType::LinkPreview; + Q_EMIT dataChanged(index(it - m_components.begin()), index(it - m_components.begin()), {ComponentTypeRole}); } - } - }); - return MessageComponent{MessageComponentType::LinkPreviewLoad, QString(), {{"link"_L1, link}}}; - } -} - -QList MessageContentModel::addLinkPreviews(QList inputComponents) -{ - int i = 0; - while (i < inputComponents.size()) { - const auto component = inputComponents.at(i); - if (component.type == MessageComponentType::Text || component.type == MessageComponentType::Quote) { - if (LinkPreviewer::hasPreviewableLinks(component.content)) { - const auto links = LinkPreviewer::linkPreviews(component.content); - for (qsizetype j = 0; j < links.size(); ++j) { - const auto linkPreview = linkPreviewComponent(links[j]); - if (!m_removedLinkPreviews.contains(links[j]) && !linkPreview.isEmpty()) { - inputComponents.insert(i + j + 1, linkPreview); - } - }; - } + return it; + }); } - i++; - } - - return inputComponents; + }); + return MessageComponent{MessageComponentType::LinkPreviewLoad, QString(), {{"link"_L1, link}}}; } void MessageContentModel::closeLinkPreview(int row) diff --git a/src/messagecontent/models/messagecontentmodel.h b/src/messagecontent/models/messagecontentmodel.h index 214b706c7..153d93a46 100644 --- a/src/messagecontent/models/messagecontentmodel.h +++ b/src/messagecontent/models/messagecontentmodel.h @@ -9,6 +9,7 @@ #include #include "enums/messagecomponenttype.h" +#include "linkpreviewer.h" #include "messagecomponent.h" #include "models/itinerarymodel.h" #include "models/reactionmodel.h" @@ -133,13 +134,32 @@ private: void initializeEvent(); void getEvent(); + using ComponentIt = QList::iterator; + QList m_components; bool hasComponentType(MessageComponentType::Type type); - void forEachComponentOfType(MessageComponentType::Type type, std::function function); - void forEachComponentOfType(QList types, std::function function); + void forEachComponentOfType(MessageComponentType::Type type, std::function function); + void forEachComponentOfType(QList types, std::function function); - std::function m_fileInfoFunction = [this](const QModelIndex &index) { - Q_EMIT dataChanged(index, index, {MessageContentModel::FileTransferInfoRole}); + std::function m_fileInfoFunction = [this](ComponentIt it) { + Q_EMIT dataChanged(index(it - m_components.begin()), index(it - m_components.begin()), {MessageContentModel::FileTransferInfoRole}); + return ++it; + }; + std::function m_linkPreviewFunction = [this](ComponentIt it) { + bool previewAdded = false; + if (LinkPreviewer::hasPreviewableLinks(it->content)) { + const auto links = LinkPreviewer::linkPreviews(it->content); + for (qsizetype j = 0; j < links.size(); ++j) { + const auto linkPreview = linkPreviewComponent(links[j]); + if (!m_removedLinkPreviews.contains(links[j]) && !linkPreview.isEmpty()) { + beginInsertRows({}, std::distance(m_components.begin(), it) + j + 1, std::distance(m_components.begin(), it) + j + 1); + it = m_components.insert(it + j + 1, linkPreview); + previewAdded = true; + endInsertRows(); + } + }; + } + return previewAdded ? it : ++it; }; void resetModel(); @@ -154,7 +174,6 @@ private: QList componentsForType(MessageComponentType::Type type); MessageComponent linkPreviewComponent(const QUrl &link); - QList addLinkPreviews(QList inputComponents); QList m_removedLinkPreviews;