From e2eb6ab33c0a1ccf6685bacdb977057750626ec4 Mon Sep 17 00:00:00 2001 From: Joshua Goins Date: Thu, 14 Mar 2024 19:22:31 -0400 Subject: [PATCH] Don't destroy formatting when editing previous messages Adds a few new methods to grab the markdown/slightly rich text from the message, and will intelligently re-insert user mentions as needed. --- autotests/eventhandlertest.cpp | 8 +++++ src/chatbarcache.cpp | 51 +++++++++++++++++++++++++++++++- src/chatbarcache.h | 8 +++++ src/eventhandler.cpp | 16 ++++++++++ src/eventhandler.h | 7 +++++ src/qml/MessageEditComponent.qml | 3 ++ 6 files changed, 92 insertions(+), 1 deletion(-) diff --git a/autotests/eventhandlertest.cpp b/autotests/eventhandlertest.cpp index 3c7d552cd..7df92639a 100644 --- a/autotests/eventhandlertest.cpp +++ b/autotests/eventhandlertest.cpp @@ -55,6 +55,7 @@ private Q_SLOTS: void genericBody_data(); void genericBody(); void nullGenericBody(); + void markdownBody(); void subtitle(); void nullSubtitle(); void mediaInfo(); @@ -293,6 +294,13 @@ void EventHandlerTest::nullGenericBody() QCOMPARE(noEventHandler.getGenericBody(), QString()); } +void EventHandlerTest::markdownBody() +{ + EventHandler eventHandler(room, room->messageEvents().at(0).get()); + + QCOMPARE(eventHandler.getMarkdownBody(), QStringLiteral("This is an example\ntext message")); +} + void EventHandlerTest::subtitle() { EventHandler eventHandler(room, room->messageEvents().at(0).get()); diff --git a/src/chatbarcache.cpp b/src/chatbarcache.cpp index 17f2a47a4..d123db5f3 100644 --- a/src/chatbarcache.cpp +++ b/src/chatbarcache.cpp @@ -3,6 +3,7 @@ #include "chatbarcache.h" +#include "chatdocumenthandler.h" #include "eventhandler.h" #include "neochatroom.h" @@ -117,7 +118,7 @@ QString ChatBarCache::relationMessage() const if (auto event = room->findInTimeline(m_relationId); event != room->historyEdge()) { EventHandler eventhandler(room, &**event); - return eventhandler.getPlainBody(); + return eventhandler.getMarkdownBody(); } return {}; } @@ -163,6 +164,54 @@ QList *ChatBarCache::mentions() return &m_mentions; } +void ChatBarCache::updateMentions(QQuickTextDocument *document, ChatDocumentHandler *documentHandler) +{ + documentHandler->setDocument(document); + + if (parent() == nullptr) { + qWarning() << "ChatBarCache created with no parent, a NeoChatRoom must be set as the parent on creation."; + return; + } + if (m_relationId.isEmpty()) { + return; + } + auto room = dynamic_cast(parent()); + if (room == nullptr) { + qWarning() << "ChatBarCache created with incorrect parent, a NeoChatRoom must be set as the parent on creation."; + return; + } + + if (auto event = room->findInTimeline(m_relationId); event != room->historyEdge()) { + if (const auto &roomMessageEvent = &*event->viewAs()) { + // Replaces the mentions that are baked into the HTML but plaintext in the original markdown + const QRegularExpression re(QStringLiteral(R"lit(([\S]*)<\/a>)lit")); + + m_mentions.clear(); + + int linkSize = 0; + auto matches = re.globalMatch(EventHandler::rawMessageBody(*roomMessageEvent)); + while (matches.hasNext()) { + const QRegularExpressionMatch match = matches.next(); + if (match.hasMatch()) { + const QString id = match.captured(1); + const QString name = match.captured(2); + + const int position = match.capturedStart(0) - linkSize; + const int end = position + name.length(); + linkSize += match.capturedLength(0) - name.length(); + + QTextCursor cursor(documentHandler->document()->textDocument()); + cursor.setPosition(position); + cursor.setPosition(end, QTextCursor::KeepAnchor); + cursor.setKeepPositionOnInsert(true); + + m_mentions.push_back(Mention{.cursor = cursor, .text = name, .start = position, .position = end, .id = id}); + } + } + } + } +} + QString ChatBarCache::savedText() const { return m_savedText; diff --git a/src/chatbarcache.h b/src/chatbarcache.h index 9e3d4bef2..3dbbe8435 100644 --- a/src/chatbarcache.h +++ b/src/chatbarcache.h @@ -5,8 +5,11 @@ #include #include +#include #include +class ChatDocumentHandler; + /** * @brief Defines a user mention in the current chat or edit text. */ @@ -174,6 +177,11 @@ public: */ QList *mentions(); + /** + * @brief Update the mentions in @p document when editing a message. + */ + Q_INVOKABLE void updateMentions(QQuickTextDocument *document, ChatDocumentHandler *documentHandler); + /** * @brief Get the saved chat bar text. */ diff --git a/src/eventhandler.cpp b/src/eventhandler.cpp index 4353a15c0..a35f2acd5 100644 --- a/src/eventhandler.cpp +++ b/src/eventhandler.cpp @@ -280,6 +280,22 @@ QString EventHandler::getPlainBody(bool stripNewlines) const return getBody(m_event, Qt::PlainText, stripNewlines); } +QString EventHandler::getMarkdownBody() const +{ + if (m_event == nullptr) { + qCWarning(EventHandling) << "getMarkdownBody called with m_event set to nullptr."; + return {}; + } + + if (!m_event->is()) { + qCWarning(EventHandling) << "getMarkdownBody called when m_event isn't a RoomMessageEvent."; + return {}; + } + + const auto roomMessageEvent = eventCast(m_event); + return roomMessageEvent->plainBody(); +} + QString EventHandler::getBody(const Quotient::RoomEvent *event, Qt::TextFormat format, bool stripNewlines) const { if (event->isRedacted()) { diff --git a/src/eventhandler.h b/src/eventhandler.h index 80f98e662..c6d2da10c 100644 --- a/src/eventhandler.h +++ b/src/eventhandler.h @@ -185,6 +185,13 @@ public: */ QString getPlainBody(bool stripNewlines = false) const; + /** + * @brief Output the original body for the message content, useful for editing the original message. + * + * The event type must be a room message event. + */ + QString getMarkdownBody() const; + /** * @brief Output a generic string for the message content ready for display. * diff --git a/src/qml/MessageEditComponent.qml b/src/qml/MessageEditComponent.qml index 3f212fef6..7e188aa31 100644 --- a/src/qml/MessageEditComponent.qml +++ b/src/qml/MessageEditComponent.qml @@ -170,8 +170,11 @@ QQC2.TextArea { onChatBarCacheChanged: documentHandler.chatBarCache = chatBarCache function updateEditText() { + // This could possibly be undefined due to some esoteric QtQuick issue. Referencing it somewhere in JS is enough. + documentHandler.textDocument; if (chatBarCache?.isEditing && chatBarCache.relationMessage.length > 0) { root.text = chatBarCache.relationMessage; + chatBarCache.updateMentions(root.textDocument, documentHandler); root.forceActiveFocus(); root.cursorPosition = root.length; }