diff --git a/autotests/CMakeLists.txt b/autotests/CMakeLists.txt
index 0a52904af..bea7e4fe5 100644
--- a/autotests/CMakeLists.txt
+++ b/autotests/CMakeLists.txt
@@ -76,3 +76,9 @@ ecm_add_test(
LINK_LIBRARIES neochat Qt::Test
TEST_NAME reactionmodeltest
)
+
+ecm_add_test(
+ linkpreviewertest.cpp
+ LINK_LIBRARIES neochat Qt::Test
+ TEST_NAME linkpreviewertest
+)
diff --git a/autotests/data/test-invalidmatrixtolink-event.json b/autotests/data/test-invalidmatrixtolink-event.json
new file mode 100644
index 000000000..c7b27a5a0
--- /dev/null
+++ b/autotests/data/test-invalidmatrixtolink-event.json
@@ -0,0 +1,14 @@
+{
+ "content": {
+ "body": "https://matrix.to/#/@alice:example.org",
+ "msgtype": "m.text"
+ },
+ "event_id": "$validlink1:example.org",
+ "origin_server_ts": 1432735824654,
+ "room_id": "!test:example.org",
+ "sender": "@example:example.org",
+ "type": "m.room.message",
+ "unsigned": {
+ "age": 1234
+ }
+}
diff --git a/autotests/data/test-invalidmxclink-event.json b/autotests/data/test-invalidmxclink-event.json
new file mode 100644
index 000000000..dfb19dbbc
--- /dev/null
+++ b/autotests/data/test-invalidmxclink-event.json
@@ -0,0 +1,14 @@
+{
+ "content": {
+ "body": "mxc://example.org/SEsfnsuifSDFSSEF",
+ "msgtype": "m.text"
+ },
+ "event_id": "$validlink1:example.org",
+ "origin_server_ts": 1432735824654,
+ "room_id": "!test:example.org",
+ "sender": "@example:example.org",
+ "type": "m.room.message",
+ "unsigned": {
+ "age": 1234
+ }
+}
diff --git a/autotests/data/test-invalidnospacelink-event.json b/autotests/data/test-invalidnospacelink-event.json
new file mode 100644
index 000000000..f4e7523a1
--- /dev/null
+++ b/autotests/data/test-invalidnospacelink-event.json
@@ -0,0 +1,14 @@
+{
+ "content": {
+ "body": "testhttps://kde.org",
+ "msgtype": "m.text"
+ },
+ "event_id": "$validlink1:example.org",
+ "origin_server_ts": 1432735824654,
+ "room_id": "!test:example.org",
+ "sender": "@example:example.org",
+ "type": "m.room.message",
+ "unsigned": {
+ "age": 1234
+ }
+}
diff --git a/autotests/data/test-linkpreviewerintial-sync.json b/autotests/data/test-linkpreviewerintial-sync.json
new file mode 100644
index 000000000..70366d1dc
--- /dev/null
+++ b/autotests/data/test-linkpreviewerintial-sync.json
@@ -0,0 +1,24 @@
+{
+ "timeline": {
+ "events": [
+ {
+ "content": {
+ "body": "https://kde.org",
+ "format": "org.matrix.custom.html",
+ "formatted_body": "https://kde.org",
+ "msgtype": "m.text"
+ },
+ "origin_server_ts": 1704648567967,
+ "sender": "@example:example.org",
+ "type": "m.room.message",
+ "unsigned": {
+ "age": 112
+ },
+ "event_id": "$validlink:example.org",
+ "room_id": "!test:example.org"
+ }
+ ],
+ "limited": true,
+ "prev_batch": "t34-23535_0_0"
+ }
+}
diff --git a/autotests/data/test-linkpreviewerreplace-sync.json b/autotests/data/test-linkpreviewerreplace-sync.json
new file mode 100644
index 000000000..ffbe8e245
--- /dev/null
+++ b/autotests/data/test-linkpreviewerreplace-sync.json
@@ -0,0 +1,35 @@
+{
+ "timeline": {
+ "events": [
+ {
+ "content": {
+ "body": "* ",
+ "format": "org.matrix.custom.html",
+ "formatted_body": "no link",
+ "m.new_content": {
+ "body": "",
+ "format": "org.matrix.custom.html",
+ "formatted_body": "no link",
+ "msgtype": "m.text"
+ },
+ "m.relates_to": {
+ "event_id": "$validlink:example.org",
+ "rel_type": "m.replace"
+ },
+ "msgtype": "m.text",
+ "type": "m.room.message"
+ },
+ "origin_server_ts": 1704648614969,
+ "sender": "@example:example.org",
+ "type": "m.room.message",
+ "unsigned": {
+ "age": 65
+ },
+ "event_id": "$nolink:example.org",
+ "room_id": "!test:example.org"
+ }
+ ],
+ "limited": true,
+ "prev_batch": "t34-23535_0_0"
+ }
+}
diff --git a/autotests/data/test-multiplelink-event.json b/autotests/data/test-multiplelink-event.json
new file mode 100644
index 000000000..73d5c7c15
--- /dev/null
+++ b/autotests/data/test-multiplelink-event.json
@@ -0,0 +1,14 @@
+{
+ "content": {
+ "body": "www.example.org https://kde.org",
+ "msgtype": "m.text"
+ },
+ "event_id": "$validlink1:example.org",
+ "origin_server_ts": 1432735824654,
+ "room_id": "!test:example.org",
+ "sender": "@example:example.org",
+ "type": "m.room.message",
+ "unsigned": {
+ "age": 1234
+ }
+}
diff --git a/autotests/data/test-validplainlink-event.json b/autotests/data/test-validplainlink-event.json
new file mode 100644
index 000000000..4a326cd11
--- /dev/null
+++ b/autotests/data/test-validplainlink-event.json
@@ -0,0 +1,14 @@
+{
+ "content": {
+ "body": "https://kde.org",
+ "msgtype": "m.text"
+ },
+ "event_id": "$validlink1:example.org",
+ "origin_server_ts": 1432735824654,
+ "room_id": "!test:example.org",
+ "sender": "@example:example.org",
+ "type": "m.room.message",
+ "unsigned": {
+ "age": 1234
+ }
+}
diff --git a/autotests/data/test-validplainwwwlink-event.json b/autotests/data/test-validplainwwwlink-event.json
new file mode 100644
index 000000000..b586b419f
--- /dev/null
+++ b/autotests/data/test-validplainwwwlink-event.json
@@ -0,0 +1,14 @@
+{
+ "content": {
+ "body": "www.example.org",
+ "msgtype": "m.text"
+ },
+ "event_id": "$validlink1:example.org",
+ "origin_server_ts": 1432735824654,
+ "room_id": "!test:example.org",
+ "sender": "@example:example.org",
+ "type": "m.room.message",
+ "unsigned": {
+ "age": 1234
+ }
+}
diff --git a/autotests/data/test-validrichlink-event.json b/autotests/data/test-validrichlink-event.json
new file mode 100644
index 000000000..c8e793b80
--- /dev/null
+++ b/autotests/data/test-validrichlink-event.json
@@ -0,0 +1,16 @@
+{
+ "content": {
+ "body": "[Rich Link](https://kde.org)",
+ "format": "org.matrix.custom.html",
+ "formatted_body": "Rich Link",
+ "msgtype": "m.text"
+ },
+ "event_id": "$validlink1:example.org",
+ "origin_server_ts": 1432735824654,
+ "room_id": "!test:example.org",
+ "sender": "@example:example.org",
+ "type": "m.room.message",
+ "unsigned": {
+ "age": 1234
+ }
+}
diff --git a/autotests/eventhandlertest.cpp b/autotests/eventhandlertest.cpp
index 43c58e9d7..61ff738ed 100644
--- a/autotests/eventhandlertest.cpp
+++ b/autotests/eventhandlertest.cpp
@@ -65,8 +65,6 @@ private Q_SLOTS:
void nullSubtitle();
void mediaInfo();
void nullMediaInfo();
- void linkPreviewer();
- void nullLinkPreviewer();
void hasReply();
void nullHasReply();
void replyId();
@@ -399,28 +397,6 @@ void EventHandlerTest::nullMediaInfo()
QCOMPARE(noEventHandler.getMediaInfo(), QVariantMap());
}
-void EventHandlerTest::linkPreviewer()
-{
- auto event = room->messageEvents().at(2).get();
- eventHandler.setEvent(event);
-
- QCOMPARE(eventHandler.getLinkPreviewer()->url(), QUrl("https://kde.org"_ls));
-
- event = room->messageEvents().at(0).get();
- eventHandler.setEvent(event);
-
- QCOMPARE(eventHandler.getLinkPreviewer(), nullptr);
-}
-
-void EventHandlerTest::nullLinkPreviewer()
-{
- QTest::ignoreMessage(QtWarningMsg, "getLinkPreviewer called with m_room set to nullptr.");
- QCOMPARE(emptyHandler.getLinkPreviewer(), nullptr);
-
- QTest::ignoreMessage(QtWarningMsg, "getLinkPreviewer called with m_event set to nullptr.");
- QCOMPARE(noEventHandler.getLinkPreviewer(), nullptr);
-}
-
void EventHandlerTest::hasReply()
{
auto event = room->messageEvents().at(5).get();
diff --git a/autotests/linkpreviewertest.cpp b/autotests/linkpreviewertest.cpp
new file mode 100644
index 000000000..487b58355
--- /dev/null
+++ b/autotests/linkpreviewertest.cpp
@@ -0,0 +1,104 @@
+// SPDX-FileCopyrightText: 2023 James Graham
+// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
+
+#include
+#include
+
+#include "linkpreviewer.h"
+
+#include
+#include
+#include
+
+#include "utils.h"
+
+#include "testutils.h"
+
+using namespace Quotient;
+
+class LinkPreviewerTest : public QObject
+{
+ Q_OBJECT
+
+private:
+ Connection *connection = nullptr;
+ TestUtils::TestRoom *room = nullptr;
+
+private Q_SLOTS:
+ void initTestCase();
+
+ void linkPreviewsMatch_data();
+ void linkPreviewsMatch();
+
+ void linkPreviewsReject_data();
+ void linkPreviewsReject();
+
+ void editedLink();
+};
+
+void LinkPreviewerTest::initTestCase()
+{
+ connection = Connection::makeMockConnection(QStringLiteral("@bob:example.org"));
+ room = new TestUtils::TestRoom(connection, QStringLiteral("!test:example.org"));
+}
+
+void LinkPreviewerTest::linkPreviewsMatch_data()
+{
+ QTest::addColumn("eventSource");
+ QTest::addColumn("testOutputLink");
+
+ QTest::newRow("plainHttps") << QStringLiteral("test-validplainlink-event.json") << QUrl("https://kde.org"_ls);
+ QTest::newRow("richHttps") << QStringLiteral("test-validrichlink-event.json") << QUrl("https://kde.org"_ls);
+ QTest::newRow("plainWww") << QStringLiteral("test-validplainwwwlink-event.json") << QUrl("www.example.org"_ls);
+ QTest::newRow("multipleHttps") << QStringLiteral("test-multiplelink-event.json") << QUrl("www.example.org"_ls);
+}
+
+void LinkPreviewerTest::linkPreviewsMatch()
+{
+ QFETCH(QString, eventSource);
+ QFETCH(QUrl, testOutputLink);
+
+ auto event = TestUtils::loadEventFromFile(eventSource);
+ auto linkPreviewer = LinkPreviewer(room, event.get());
+
+ QCOMPARE(linkPreviewer.empty(), false);
+ QCOMPARE(linkPreviewer.url(), testOutputLink);
+}
+
+void LinkPreviewerTest::linkPreviewsReject_data()
+{
+ QTest::addColumn("eventSource");
+
+ QTest::newRow("mxc") << QStringLiteral("test-invalidmxclink-event.json");
+ QTest::newRow("matrixTo") << QStringLiteral("test-invalidmatrixtolink-event.json");
+ QTest::newRow("noSpace") << QStringLiteral("test-invalidnospacelink-event.json");
+}
+
+void LinkPreviewerTest::linkPreviewsReject()
+{
+ QFETCH(QString, eventSource);
+
+ auto event = TestUtils::loadEventFromFile(eventSource);
+ auto linkPreviewer = LinkPreviewer(room, event.get());
+
+ QCOMPARE(linkPreviewer.empty(), true);
+ QCOMPARE(linkPreviewer.url(), QUrl());
+}
+
+void LinkPreviewerTest::editedLink()
+{
+ room->syncNewEvents(QStringLiteral("test-linkpreviewerintial-sync.json"));
+ auto event = eventCast(room->messageEvents().at(0).get());
+ auto linkPreviewer = LinkPreviewer(room, event);
+
+ QCOMPARE(linkPreviewer.empty(), false);
+ QCOMPARE(linkPreviewer.url(), QUrl("https://kde.org"_ls));
+
+ room->syncNewEvents(QStringLiteral("test-linkpreviewerreplace-sync.json"));
+
+ QCOMPARE(linkPreviewer.empty(), true);
+ QCOMPARE(linkPreviewer.url(), QUrl());
+}
+
+QTEST_MAIN(LinkPreviewerTest)
+#include "linkpreviewertest.moc"
diff --git a/autotests/testutils.h b/autotests/testutils.h
index b32d9cd43..bebf8f7e4 100644
--- a/autotests/testutils.h
+++ b/autotests/testutils.h
@@ -1,6 +1,7 @@
// SPDX-FileCopyrightText: 2023 James Graham
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
+#include
#include
#include "neochatroom.h"
@@ -38,4 +39,17 @@ public:
}
}
};
+
+template
+inline Quotient::event_ptr_tt loadEventFromFile(const QString &eventFileName)
+{
+ if (!eventFileName.isEmpty()) {
+ QFile testEventFile;
+ testEventFile.setFileName(QLatin1String(DATA_DIR) + u'/' + eventFileName);
+ testEventFile.open(QIODevice::ReadOnly);
+ auto testSyncJson = QJsonDocument::fromJson(testEventFile.readAll()).object();
+ return Quotient::loadEvent(testSyncJson);
+ }
+ return nullptr;
+}
}
diff --git a/autotests/texthandlertest.cpp b/autotests/texthandlertest.cpp
index 5eff65e66..223f03c0e 100644
--- a/autotests/texthandlertest.cpp
+++ b/autotests/texthandlertest.cpp
@@ -64,11 +64,6 @@ private Q_SLOTS:
void receiveRichEdited();
void receiveLineSeparator();
void receiveRichCodeUrl();
-
- void linkPreviewsMatch_data();
- void linkPreviewsMatch();
- void linkPreviewsReject_data();
- void linkPreviewsReject();
};
void TextHandlerTest::initTestCase()
@@ -523,53 +518,6 @@ void TextHandlerTest::receiveLineSeparator()
QCOMPARE(textHandler.handleRecievePlainText(Qt::PlainText, true), QStringLiteral("foo bar"));
}
-void TextHandlerTest::linkPreviewsMatch_data()
-{
- QTest::addColumn("testInputString");
- QTest::addColumn>("testOutputLinks");
-
- QTest::newRow("plainHttps") << QStringLiteral("https://kde.org") << QList({QUrl("https://kde.org"_ls)});
- QTest::newRow("richHttps") << QStringLiteral("Rich Link") << QList({QUrl("https://kde.org"_ls)});
- QTest::newRow("plainWww") << QStringLiteral("www.example.org") << QList({QUrl("www.example.org"_ls)});
- QTest::newRow("multipleHttps") << QStringLiteral("https://kde.org www.example.org")
- << QList({
- QUrl("https://kde.org"_ls),
- QUrl("www.example.org"_ls),
- });
-}
-
-void TextHandlerTest::linkPreviewsMatch()
-{
- QFETCH(QString, testInputString);
- QFETCH(QList, testOutputLinks);
-
- TextHandler testTextHandler;
- testTextHandler.setData(testInputString);
-
- QCOMPARE(testTextHandler.getLinkPreviews(), testOutputLinks);
-}
-
-void TextHandlerTest::linkPreviewsReject_data()
-{
- QTest::addColumn("testInputString");
- QTest::addColumn>("testOutputLinks");
-
- QTest::newRow("mxc") << QStringLiteral("mxc://example.org/SEsfnsuifSDFSSEF") << QList();
- QTest::newRow("matrixTo") << QStringLiteral("https://matrix.to/#/@alice:example.org") << QList();
- QTest::newRow("noSpace") << QStringLiteral("testhttps://kde.org") << QList();
-}
-
-void TextHandlerTest::linkPreviewsReject()
-{
- QFETCH(QString, testInputString);
- QFETCH(QList, testOutputLinks);
-
- TextHandler testTextHandler;
- testTextHandler.setData(testInputString);
-
- QCOMPARE(testTextHandler.getLinkPreviews(), testOutputLinks);
-}
-
void TextHandlerTest::receiveRichCodeUrl()
{
auto input = QStringLiteral("https://kde.org");
diff --git a/src/eventhandler.cpp b/src/eventhandler.cpp
index 1d30cea56..6b337366e 100644
--- a/src/eventhandler.cpp
+++ b/src/eventhandler.cpp
@@ -777,43 +777,6 @@ QVariantMap EventHandler::getMediaInfoFromFileInfo(const EventContent::FileInfo
return mediaInfo;
}
-QSharedPointer EventHandler::getLinkPreviewer() const
-{
- if (m_room == nullptr) {
- qCWarning(EventHandling) << "getLinkPreviewer called with m_room set to nullptr.";
- return nullptr;
- }
- if (m_event == nullptr) {
- qCWarning(EventHandling) << "getLinkPreviewer called with m_event set to nullptr.";
- return nullptr;
- }
- if (!m_event->is()) {
- return nullptr;
- }
-
- QString text;
- auto event = eventCast(m_event);
- if (event->hasTextContent()) {
- auto textContent = static_cast(event->content());
- if (textContent) {
- text = textContent->body;
- } else {
- text = event->plainBody();
- }
- } else {
- text = event->plainBody();
- }
- TextHandler textHandler;
- textHandler.setData(text);
-
- QList links = textHandler.getLinkPreviews();
- if (links.size() > 0) {
- return QSharedPointer(new LinkPreviewer(nullptr, m_room, links.size() > 0 ? links[0] : QUrl()));
- } else {
- return nullptr;
- }
-}
-
bool EventHandler::hasReply() const
{
if (m_event == nullptr) {
diff --git a/src/eventhandler.h b/src/eventhandler.h
index 93aa792da..54378696c 100644
--- a/src/eventhandler.h
+++ b/src/eventhandler.h
@@ -231,16 +231,6 @@ public:
*/
QVariantMap getMediaInfo() const;
- /**
- * @brief Return a LinkPreviewer object for the event.
- *
- * A nullptr will be returned for any event that doesn't have any links so the
- * return should be null checked and an empty LinkPreviewer provided if null.
- *
- * @sa LinkPreviewer
- */
- QSharedPointer getLinkPreviewer() const;
-
/**
* @brief Whether the event is a reply to another in the timeline.
*/
diff --git a/src/linkpreviewer.cpp b/src/linkpreviewer.cpp
index 03e8fc96f..c82560089 100644
--- a/src/linkpreviewer.cpp
+++ b/src/linkpreviewer.cpp
@@ -3,25 +3,37 @@
#include "linkpreviewer.h"
-#include "controller.h"
-
#include
#include
+#include
#include "neochatconfig.h"
#include "neochatroom.h"
+#include "utils.h"
using namespace Quotient;
-LinkPreviewer::LinkPreviewer(QObject *parent, const NeoChatRoom *room, const QUrl &url)
- : QObject(parent)
+LinkPreviewer::LinkPreviewer(const NeoChatRoom *room, const Quotient::RoomMessageEvent *event)
+ : QObject(nullptr)
, m_currentRoom(room)
+ , m_event(event)
, m_loaded(false)
- , m_url(url)
+ , m_url(linkPreview(event))
{
- loadUrlPreview();
- if (m_currentRoom) {
+ connect(this, &LinkPreviewer::urlChanged, this, &LinkPreviewer::emptyChanged);
+
+ if (m_event != nullptr && m_currentRoom != nullptr) {
+ loadUrlPreview();
connect(m_currentRoom, &NeoChatRoom::urlPreviewEnabledChanged, this, &LinkPreviewer::loadUrlPreview);
+ // Make sure that we react to edits
+ connect(m_currentRoom, &NeoChatRoom::replacedEvent, this, [this](const Quotient::RoomEvent *newEvent) {
+ if (m_event->id() == newEvent->id()) {
+ m_event = eventCast(newEvent);
+ m_url = linkPreview(m_event);
+ Q_EMIT urlChanged();
+ loadUrlPreview();
+ }
+ });
}
connect(NeoChatConfig::self(), &NeoChatConfig::ShowLinkPreviewChanged, this, &LinkPreviewer::loadUrlPreview);
}
@@ -51,15 +63,6 @@ QUrl LinkPreviewer::url() const
return m_url;
}
-void LinkPreviewer::setUrl(QUrl url)
-{
- if (url != m_url) {
- m_url = url;
- urlChanged();
- loadUrlPreview();
- }
-}
-
void LinkPreviewer::loadUrlPreview()
{
if (!m_currentRoom || !NeoChatConfig::showLinkPreview() || !m_currentRoom->urlPreviewEnabled()) {
@@ -98,4 +101,38 @@ bool LinkPreviewer::empty() const
return m_url.isEmpty();
}
+QUrl LinkPreviewer::linkPreview(const Quotient::RoomMessageEvent *event)
+{
+ if (event == nullptr) {
+ return {};
+ }
+
+ QString text;
+ if (event->hasTextContent()) {
+ auto textContent = static_cast(event->content());
+ if (textContent) {
+ text = textContent->body;
+ } else {
+ text = event->plainBody();
+ }
+ } else {
+ text = event->plainBody();
+ }
+
+ auto data = text.remove(TextRegex::removeRichReply);
+ auto linksMatch = TextRegex::url.globalMatch(data);
+ while (linksMatch.hasNext()) {
+ auto link = linksMatch.next().captured();
+ if (!link.contains(QStringLiteral("matrix.to"))) {
+ return QUrl(link);
+ }
+ }
+ return {};
+}
+
+bool LinkPreviewer::hasPreviewableLinks(const Quotient::RoomMessageEvent *event)
+{
+ return !linkPreview(event).isEmpty();
+}
+
#include "moc_linkpreviewer.cpp"
diff --git a/src/linkpreviewer.h b/src/linkpreviewer.h
index 00c4cf1f2..5e85f422f 100644
--- a/src/linkpreviewer.h
+++ b/src/linkpreviewer.h
@@ -7,6 +7,11 @@
#include
#include
+namespace Quotient
+{
+class RoomMessageEvent;
+}
+
class NeoChatRoom;
/**
@@ -25,7 +30,7 @@ class LinkPreviewer : public QObject
/**
* @brief The URL to get the preview for.
*/
- Q_PROPERTY(QUrl url READ url WRITE setUrl NOTIFY urlChanged)
+ Q_PROPERTY(QUrl url READ url NOTIFY urlChanged)
/**
* @brief Whether the preview information has been loaded.
@@ -55,18 +60,25 @@ class LinkPreviewer : public QObject
Q_PROPERTY(bool empty READ empty NOTIFY emptyChanged)
public:
- explicit LinkPreviewer(QObject *parent = nullptr, const NeoChatRoom *room = nullptr, const QUrl &url = {});
+ explicit LinkPreviewer(const NeoChatRoom *room = nullptr, const Quotient::RoomMessageEvent *event = nullptr);
[[nodiscard]] QUrl url() const;
- void setUrl(QUrl);
[[nodiscard]] bool loaded() const;
[[nodiscard]] QString title() const;
[[nodiscard]] QString description() const;
[[nodiscard]] QUrl imageSource() const;
[[nodiscard]] bool empty() const;
+ /**
+ * @brief Whether the given event has at least 1 pre-viewable link.
+ *
+ * A link is only pre-viewable if it is http, https or something starting with www.
+ */
+ static bool hasPreviewableLinks(const Quotient::RoomMessageEvent *event);
+
private:
- const NeoChatRoom *m_currentRoom = nullptr;
+ const NeoChatRoom *m_currentRoom;
+ const Quotient::RoomMessageEvent *m_event;
bool m_loaded;
QString m_title = QString();
@@ -76,6 +88,14 @@ private:
void loadUrlPreview();
+ /**
+ * @brief Return the link to be previewed from the given event.
+ *
+ * This function is designed to give only links that should be previewed so
+ * http, https or something starting with www. The first valid link is returned.
+ */
+ static QUrl linkPreview(const Quotient::RoomMessageEvent *event);
+
Q_SIGNALS:
void loadedChanged();
void titleChanged();
diff --git a/src/models/messageeventmodel.cpp b/src/models/messageeventmodel.cpp
index 0e6e48f31..f93f98e65 100644
--- a/src/models/messageeventmodel.cpp
+++ b/src/models/messageeventmodel.cpp
@@ -2,6 +2,7 @@
// SPDX-License-Identifier: GPL-3.0-only
#include "messageeventmodel.h"
+#include "linkpreviewer.h"
#include "messageeventmodel_logging.h"
#include "neochatconfig.h"
@@ -22,6 +23,7 @@
#include "eventhandler.h"
#include "events/pollevent.h"
#include "models/reactionmodel.h"
+#include "texthandler.h"
using namespace Quotient;
@@ -237,6 +239,10 @@ void MessageEventModel::setRoom(NeoChatRoom *room)
});
connect(m_currentRoom, &Room::replacedEvent, this, [this](const RoomEvent *newEvent) {
refreshLastUserEvents(refreshEvent(newEvent->id()) - timelineBaseIndex());
+ const RoomMessageEvent *message = eventCast(newEvent);
+ if (message != nullptr) {
+ createEventObjects(message);
+ }
});
connect(m_currentRoom, &Room::updatedEvent, this, [this](const QString &eventId) {
if (eventId.isEmpty()) { // How did we get here?
@@ -720,14 +726,14 @@ void MessageEventModel::createEventObjects(const Quotient::RoomMessageEvent *eve
{
auto eventId = event->id();
- EventHandler eventHandler;
- eventHandler.setRoom(m_currentRoom);
- eventHandler.setEvent(event);
-
- if (auto linkPreviewer = eventHandler.getLinkPreviewer()) {
- m_linkPreviewers[eventId] = linkPreviewer;
+ if (m_linkPreviewers.contains(eventId)) {
+ if (!LinkPreviewer::hasPreviewableLinks(event)) {
+ m_linkPreviewers.remove(eventId);
+ }
} else {
- m_linkPreviewers.remove(eventId);
+ if (LinkPreviewer::hasPreviewableLinks(event)) {
+ m_linkPreviewers[eventId] = QSharedPointer(new LinkPreviewer(m_currentRoom, event));
+ }
}
// ReactionModel handles updates to add and remove reactions, we only need to
diff --git a/src/texthandler.cpp b/src/texthandler.cpp
index ac3aa643d..503e149bc 100644
--- a/src/texthandler.cpp
+++ b/src/texthandler.cpp
@@ -493,18 +493,4 @@ QString TextHandler::linkifyUrls(QString stringIn)
return stringIn;
}
-QList TextHandler::getLinkPreviews()
-{
- auto data = m_data.remove(TextRegex::removeRichReply);
- auto linksMatch = TextRegex::url.globalMatch(data);
- QList links;
- while (linksMatch.hasNext()) {
- auto link = linksMatch.next().captured();
- if (!link.contains(QStringLiteral("matrix.to"))) {
- links += QUrl(link);
- }
- }
- return links;
-}
-
#include "moc_texthandler.cpp"
diff --git a/src/texthandler.h b/src/texthandler.h
index eac83e0f9..45ccb111b 100644
--- a/src/texthandler.h
+++ b/src/texthandler.h
@@ -11,29 +11,9 @@
#include "neochatroom.h"
-namespace TextRegex
+namespace Quotient
{
-static const QRegularExpression endTagType{QStringLiteral("(>| )")};
-static const QRegularExpression attributeData{QStringLiteral("['\"](.*?)['\"]")};
-static const QRegularExpression removeReply{QStringLiteral("> <.*?>.*?\\n\\n"), QRegularExpression::DotMatchesEverythingOption};
-static const QRegularExpression removeRichReply{QStringLiteral(".*?"), QRegularExpression::DotMatchesEverythingOption};
-static const QRegularExpression codePill{QStringLiteral("]*>(.*?)
"), QRegularExpression::DotMatchesEverythingOption};
-static const QRegularExpression userPill{QStringLiteral("(.*?)"), QRegularExpression::DotMatchesEverythingOption};
-static const QRegularExpression blockQuote{QStringLiteral("\n?(?:)?(.*?)(?:
)?\n?
"),
- QRegularExpression::DotMatchesEverythingOption};
-static const QRegularExpression strikethrough{QStringLiteral("(.*?)"), QRegularExpression::DotMatchesEverythingOption};
-static const QRegularExpression mxcImage{QStringLiteral(R"AAA(
)AAA")};
-static const QRegularExpression plainUrl(
- QStringLiteral(
- R"((*SKIP)(*F)|\b((www\.(?!\.)(?!(\w|\.|-)+@)|(https?|ftp):(//)?\w|(magnet|matrix):)(&(?![lg]t;)|[^&\s<>'"])+(&(?![lg]t;)|[^&!,.\s<>'"\]):])))"),
- QRegularExpression::CaseInsensitiveOption | QRegularExpression::UseUnicodePropertiesOption);
-static const QRegularExpression
- url(QStringLiteral(R"(\b((www\.(?!\.)(?!(\w|\.|-)+@)|https?:(//)?\w)(&(?![lg]t;)|[^&\s<>'"])+(&(?![lg]t;)|[^&!,.\s<>'"\]):])))"),
- QRegularExpression::CaseInsensitiveOption | QRegularExpression::UseUnicodePropertiesOption);
-static const QRegularExpression emailAddress(QStringLiteral(R"((*SKIP)(*F)|\b(mailto:)?((\w|\.|-)+@(\w|\.|-)+\.\w+\b))"),
- QRegularExpression::CaseInsensitiveOption | QRegularExpression::UseUnicodePropertiesOption);
-static const QRegularExpression mxId(QStringLiteral(R"((^|[][[:space:](){}`'";])([!#@][-a-z0-9_=#/.]{1,252}:\w(?:\w|\.|-)*\.\w+(?::\d{1,5})?))"),
- QRegularExpression::CaseInsensitiveOption | QRegularExpression::UseUnicodePropertiesOption);
+class RoomMessageEvent;
}
/**
@@ -114,14 +94,6 @@ public:
*/
QString handleRecievePlainText(Qt::TextFormat inputFormat = Qt::PlainText, const bool &stripNewlines = false);
- /**
- * @brief Return a list of links that can be previewed.
- *
- * This function is designed to give only links that should be previewed so
- * http, https or something starting with www.
- */
- QList getLinkPreviews();
-
private:
QString m_data;
diff --git a/src/utils.h b/src/utils.h
index 1c7ddc97d..66d47d775 100644
--- a/src/utils.h
+++ b/src/utils.h
@@ -4,6 +4,7 @@
#include
#include
#include
+#include
namespace Utils
{
@@ -21,3 +22,28 @@ inline QColor getUserColor(qreal hueF)
return QColor::fromHslF(hueF, 1, -0.7 * lightness + 0.9, 1);
}
}
+
+namespace TextRegex
+{
+static const QRegularExpression endTagType{QStringLiteral("(>| )")};
+static const QRegularExpression attributeData{QStringLiteral("['\"](.*?)['\"]")};
+static const QRegularExpression removeReply{QStringLiteral("> <.*?>.*?\\n\\n"), QRegularExpression::DotMatchesEverythingOption};
+static const QRegularExpression removeRichReply{QStringLiteral(".*?"), QRegularExpression::DotMatchesEverythingOption};
+static const QRegularExpression codePill{QStringLiteral("]*>(.*?)
"), QRegularExpression::DotMatchesEverythingOption};
+static const QRegularExpression userPill{QStringLiteral("(.*?)"), QRegularExpression::DotMatchesEverythingOption};
+static const QRegularExpression blockQuote{QStringLiteral("\n?(?:)?(.*?)(?:
)?\n?
"),
+ QRegularExpression::DotMatchesEverythingOption};
+static const QRegularExpression strikethrough{QStringLiteral("(.*?)"), QRegularExpression::DotMatchesEverythingOption};
+static const QRegularExpression mxcImage{QStringLiteral(R"AAA(
)AAA")};
+static const QRegularExpression plainUrl(
+ QStringLiteral(
+ R"((*SKIP)(*F)|\b((www\.(?!\.)(?!(\w|\.|-)+@)|(https?|ftp):(//)?\w|(magnet|matrix):)(&(?![lg]t;)|[^&\s<>'"])+(&(?![lg]t;)|[^&!,.\s<>'"\]):])))"),
+ QRegularExpression::CaseInsensitiveOption | QRegularExpression::UseUnicodePropertiesOption);
+static const QRegularExpression
+ url(QStringLiteral(R"(\b((www\.(?!\.)(?!(\w|\.|-)+@)|https?:(//)?\w)(&(?![lg]t;)|[^&\s<>'"])+(&(?![lg]t;)|[^&!,.\s<>'"\]):])))"),
+ QRegularExpression::CaseInsensitiveOption | QRegularExpression::UseUnicodePropertiesOption);
+static const QRegularExpression emailAddress(QStringLiteral(R"((*SKIP)(*F)|\b(mailto:)?((\w|\.|-)+@(\w|\.|-)+\.\w+\b))"),
+ QRegularExpression::CaseInsensitiveOption | QRegularExpression::UseUnicodePropertiesOption);
+static const QRegularExpression mxId(QStringLiteral(R"((^|[][[:space:](){}`'";])([!#@][-a-z0-9_=#/.]{1,252}:\w(?:\w|\.|-)*\.\w+(?::\d{1,5})?))"),
+ QRegularExpression::CaseInsensitiveOption | QRegularExpression::UseUnicodePropertiesOption);
+}