From 1d5ec4d239083b03bbca0d400c70916c99f867f7 Mon Sep 17 00:00:00 2001 From: James Graham Date: Fri, 27 Feb 2026 15:33:48 +0000 Subject: [PATCH] Plumb the adding of message edit text back in. This should also improve edits where there is code or quotes. --- src/libneochat/chatbarcache.cpp | 18 ++++++- src/libneochat/chatbarcache.h | 9 +--- src/libneochat/chattextitemhelper.cpp | 42 ++++++++++++--- src/libneochat/texthandler.cpp | 4 +- .../models/chatbarmessagecontentmodel.cpp | 54 ++++++++++++++++++- .../models/chatbarmessagecontentmodel.h | 2 + 6 files changed, 111 insertions(+), 18 deletions(-) diff --git a/src/libneochat/chatbarcache.cpp b/src/libneochat/chatbarcache.cpp index f06cee7d8..a18a2a1aa 100644 --- a/src/libneochat/chatbarcache.cpp +++ b/src/libneochat/chatbarcache.cpp @@ -135,7 +135,23 @@ QString ChatBarCache::relationMessage() const return {}; } if (auto [event, _] = m_room->getEvent(m_relationId); event != nullptr) { - return EventHandler::markdownBody(event); + return EventHandler::rawMessageBody(*event); + } + return {}; +} + +QList ChatBarCache::relationComponents() const +{ + if (!m_room) { + qCWarning(ChatBar) << "ChatBarCache:" << __FUNCTION__ << "called after room was deleted"; + return {}; + } + if (m_relationId.isEmpty()) { + return {}; + } + if (auto [event, _] = m_room->getEvent(m_relationId); event != nullptr) { + TextHandler handler; + return TextHandler().textComponents(EventHandler::rawMessageBody(*event), EventHandler::messageBodyInputFormat(*event), m_room, event); } return {}; } diff --git a/src/libneochat/chatbarcache.h b/src/libneochat/chatbarcache.h index 29746c4d0..701364e6d 100644 --- a/src/libneochat/chatbarcache.h +++ b/src/libneochat/chatbarcache.h @@ -15,6 +15,7 @@ namespace Quotient class RoomMember; } +struct MessageComponent; class NeoChatRoom; /** @@ -91,13 +92,6 @@ class ChatBarCache : public QObject */ Q_PROPERTY(bool relationAuthorIsPresent READ relationAuthorIsPresent NOTIFY relationAuthorIsPresentChanged) - /** - * @brief The content of the related message. - * - * Will be QString() if no related message. - */ - Q_PROPERTY(QString relationMessage READ relationMessage NOTIFY relationIdChanged) - /** * @brief Whether the chat bar is replying in a thread. */ @@ -147,6 +141,7 @@ public: bool relationAuthorIsPresent() const; QString relationMessage() const; + QList relationComponents() const; bool isThreaded() const; QString threadId() const; diff --git a/src/libneochat/chattextitemhelper.cpp b/src/libneochat/chattextitemhelper.cpp index cd0fe8d2c..09646c817 100644 --- a/src/libneochat/chattextitemhelper.cpp +++ b/src/libneochat/chattextitemhelper.cpp @@ -9,7 +9,6 @@ #include #include -#include #include "chatbarsyntaxhighlighter.h" #include "neochatroom.h" @@ -165,23 +164,52 @@ void ChatTextItemHelper::initialize() int finalCursorPos = cursor.position(); if (doc->isEmpty() && !m_initialFragment.isEmpty()) { cursor.insertFragment(m_initialFragment); + if (cursor.blockFormat().bottomMargin() > 0) { + auto blockFormat = cursor.blockFormat(); + blockFormat.setBottomMargin(0); + cursor.setBlockFormat(blockFormat); + } finalCursorPos = cursor.position(); } - if (!m_fixedStartChars.isEmpty() && doc->characterAt(0) != m_fixedStartChars) { + if (!m_fixedStartChars.isEmpty()) { cursor.movePosition(QTextCursor::Start); - cursor.insertText(m_fixedStartChars); - finalCursorPos += m_fixedStartChars.length(); + cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor, m_fixedStartChars.length()); + if (cursor.selectedText() != m_fixedStartChars) { + cursor.movePosition(QTextCursor::Start); + cursor.insertText(m_fixedStartChars); + finalCursorPos += m_fixedStartChars.length(); + } } - if (!m_fixedStartChars.isEmpty() && doc->characterAt(doc->characterCount()) != m_fixedStartChars) { + if (!m_fixedStartChars.isEmpty()) { cursor.movePosition(QTextCursor::End); - cursor.keepPositionOnInsert(); - cursor.insertText(m_fixedEndChars); + cursor.movePosition(QTextCursor::PreviousCharacter, QTextCursor::KeepAnchor, m_fixedEndChars.length()); + if (cursor.selectedText() != m_fixedEndChars) { + cursor.keepPositionOnInsert(); + cursor.insertText(m_fixedEndChars); + } } setCursorPosition(finalCursorPos); cursor.endEditBlock(); + qWarning() << doc->toRawText(); + const auto blockProperties = cursor.blockFormat().properties(); + for (const auto &property : blockProperties.keys()) { + qWarning() << static_cast(property) << blockProperties[property]; + } + const auto textProperties = cursor.charFormat().properties(); + for (const auto &property : textProperties.keys()) { + qWarning() << static_cast(property) << textProperties[property]; + } + const auto currentList = cursor.currentList(); + if (currentList) { + const auto listProperties = currentList->format().properties(); + for (const auto &property : listProperties.keys()) { + qWarning() << static_cast(property) << listProperties[property]; + } + } + m_initializingChars = false; } diff --git a/src/libneochat/texthandler.cpp b/src/libneochat/texthandler.cpp index 9f33be117..5729de1dc 100644 --- a/src/libneochat/texthandler.cpp +++ b/src/libneochat/texthandler.cpp @@ -402,9 +402,9 @@ QString TextHandler::stripBlockTags(QString string, const QString &tagType) cons } // This is not a normal quotation mark but U+201C - string.insert(startQuotationIndex, u'“'); + string.insert(startQuotationIndex, u"\""_s); // This is U+201D - string.insert(endQuotationIndex, u'”'); + string.insert(endQuotationIndex, u"\""_s); } return string; diff --git a/src/messagecontent/models/chatbarmessagecontentmodel.cpp b/src/messagecontent/models/chatbarmessagecontentmodel.cpp index b5a9e1d7d..5044b70ae 100644 --- a/src/messagecontent/models/chatbarmessagecontentmodel.cpp +++ b/src/messagecontent/models/chatbarmessagecontentmodel.cpp @@ -13,8 +13,10 @@ #include "enums/chatbartype.h" #include "enums/messagecomponenttype.h" #include "enums/richformat.h" +#include "messagecomponent.h" #include "messagecontentmodel.h" #include "neochatroom.h" +#include "texthandler.h" namespace { @@ -51,6 +53,18 @@ ChatBarMessageContentModel::ChatBarMessageContentModel(QObject *parent) textItem->setRoom(m_room); } } + // We can't guarantee whether room or type is intialised first so we have to handle. + if (!m_room || !unhandledTypeChange) { + return; + } + connectCache(m_room->cacheForType(*unhandledTypeChange)); + unhandledTypeChange = std::nullopt; + const auto newCache = m_room->cacheForType(m_type); + if (newCache && newCache->isEditing()) { + initializeEdit(); + return; + } + initializeFromCache(); }); connect(this, &ChatBarMessageContentModel::typeChanged, this, [this](ChatBarType::Type oldType) { for (const auto &component : std::as_const(m_components)) { @@ -59,9 +73,15 @@ ChatBarMessageContentModel::ChatBarMessageContentModel(QObject *parent) } } if (!m_room) { + unhandledTypeChange = oldType; return; } connectCache(m_room->cacheForType(oldType)); + const auto newCache = m_room->cacheForType(m_type); + if (newCache && newCache->isEditing()) { + initializeEdit(); + return; + } initializeFromCache(); }); connect(m_markdownHelper, &ChatMarkdownHelper::unhandledBlockFormat, this, &ChatBarMessageContentModel::insertStyleAtCursor); @@ -91,7 +111,7 @@ void ChatBarMessageContentModel::connectCache(ChatBarCache *oldCache) const auto currentCache = m_room->cacheForType(m_type); updateReplyModel(); if (currentCache->isEditing()) { - initializeFromCache(); + initializeEdit(); } }); connect(m_room->cacheForType(m_type), &ChatBarCache::attachmentPathChanged, this, [this]() { @@ -151,6 +171,38 @@ void ChatBarMessageContentModel::initializeFromCache() Q_EMIT focusRowChanged(); } +void ChatBarMessageContentModel::initializeEdit() +{ + clearModel(); + + const auto currentCache = m_room->cacheForType(m_type); + auto components = currentCache->relationComponents(); + if (components.isEmpty()) { + initializeModel(); + return; + } + + beginResetModel(); + std::ranges::for_each(components, [this](MessageComponent component) { + if (MessageComponentType::isTextType(component.type)) { + const auto textItemWrapper = new ChatTextItemHelper(this); + const auto initialFragment = component.type == MessageComponentType::Code ? QTextDocumentFragment::fromPlainText(component.display) + : QTextDocumentFragment::fromHtml(component.display); + textItemWrapper->setInitialFragment(initialFragment); + textItemWrapper->setRoom(m_room); + textItemWrapper->setType(m_type); + if (component.type == MessageComponentType::Quote) { + textItemWrapper->setFixedChars(u"\""_s, u"\""_s); + } + + component.attributes.insert(TextItemKey, QVariant::fromValue(textItemWrapper)); + connectTextItem(textItemWrapper); + } + m_components += component; + }); + endResetModel(); +} + ChatBarType::Type ChatBarMessageContentModel::type() const { return m_type; diff --git a/src/messagecontent/models/chatbarmessagecontentmodel.h b/src/messagecontent/models/chatbarmessagecontentmodel.h index ff927294e..b2c9dd65b 100644 --- a/src/messagecontent/models/chatbarmessagecontentmodel.h +++ b/src/messagecontent/models/chatbarmessagecontentmodel.h @@ -130,10 +130,12 @@ Q_SIGNALS: private: ChatBarType::Type m_type = ChatBarType::None; + std::optional unhandledTypeChange = std::nullopt; void connectCache(ChatBarCache *oldCache = nullptr); void initializeModel(const QString &initialText = {}); void initializeFromCache(); + void initializeEdit(); std::optional getReplyEventId() override;