Link preview messageeventmodel parameters

This move the finding of links and the creation of a `linkpreviewer` into c++.

- The links are now extracted from the text in `texthandler`
- The `messageeventmodel` now creates and stores `linkpreviewers` for events that have links in the current room.

Two new model roles have been created to let a text delegate know when the link preview should be shown (`showLinkPreview`) and pass the link previewer (`linkPreviewer`). Empty link previewer are returned where link don't exist so the qml doesn't have to have checks for whether the parameters are undefined.
This commit is contained in:
James Graham
2023-05-08 08:18:49 +00:00
committed by Tobias Fella
parent b82d3ab5ad
commit 72de7c6cfb
9 changed files with 187 additions and 73 deletions

View File

@@ -64,6 +64,11 @@ private Q_SLOTS:
void receiveRichEdited_data(); void receiveRichEdited_data();
void receiveRichEdited(); void receiveRichEdited();
void receiveLineSeparator(); void receiveLineSeparator();
void linkPreviewsMatch_data();
void linkPreviewsMatch();
void linkPreviewsReject_data();
void linkPreviewsReject();
}; };
#ifdef QUOTIENT_07 #ifdef QUOTIENT_07
@@ -596,5 +601,52 @@ void TextHandlerTest::receiveLineSeparator()
QCOMPARE(textHandler.handleRecievePlainText(Qt::PlainText, true), QStringLiteral("foo bar")); QCOMPARE(textHandler.handleRecievePlainText(Qt::PlainText, true), QStringLiteral("foo bar"));
} }
void TextHandlerTest::linkPreviewsMatch_data()
{
QTest::addColumn<QString>("testInputString");
QTest::addColumn<QList<QUrl>>("testOutputLinks");
QTest::newRow("plainHttps") << QStringLiteral("https://kde.org") << QList<QUrl>({QUrl("https://kde.org")});
QTest::newRow("richHttps") << QStringLiteral("<a href=\"https://kde.org\">Rich Link</a>") << QList<QUrl>({QUrl("https://kde.org")});
QTest::newRow("plainWww") << QStringLiteral("www.example.org") << QList<QUrl>({QUrl("www.example.org")});
QTest::newRow("multipleHttps") << QStringLiteral("https://kde.org www.example.org")
<< QList<QUrl>({
QUrl("https://kde.org"),
QUrl("www.example.org"),
});
}
void TextHandlerTest::linkPreviewsMatch()
{
QFETCH(QString, testInputString);
QFETCH(QList<QUrl>, testOutputLinks);
TextHandler testTextHandler;
testTextHandler.setData(testInputString);
QCOMPARE(testTextHandler.getLinkPreviews(), testOutputLinks);
}
void TextHandlerTest::linkPreviewsReject_data()
{
QTest::addColumn<QString>("testInputString");
QTest::addColumn<QList<QUrl>>("testOutputLinks");
QTest::newRow("mxc") << QStringLiteral("mxc://example.org/SEsfnsuifSDFSSEF") << QList<QUrl>();
QTest::newRow("matrixTo") << QStringLiteral("https://matrix.to/#/@alice:example.org") << QList<QUrl>();
QTest::newRow("noSpace") << QStringLiteral("testhttps://kde.org") << QList<QUrl>();
}
void TextHandlerTest::linkPreviewsReject()
{
QFETCH(QString, testInputString);
QFETCH(QList<QUrl>, testOutputLinks);
TextHandler testTextHandler;
testTextHandler.setData(testInputString);
QCOMPARE(testTextHandler.getLinkPreviews(), testOutputLinks);
}
QTEST_GUILESS_MAIN(TextHandlerTest) QTEST_GUILESS_MAIN(TextHandlerTest)
#include "texthandlertest.moc" #include "texthandlertest.moc"

View File

@@ -10,24 +10,13 @@
using namespace Quotient; using namespace Quotient;
LinkPreviewer::LinkPreviewer(QObject *parent) LinkPreviewer::LinkPreviewer(QObject *parent, NeoChatRoom *room, QUrl url)
: QObject(parent) : QObject(parent)
, m_currentRoom(room)
, m_loaded(false) , m_loaded(false)
, m_url(url)
{ {
} loadUrlPreview();
NeoChatRoom *LinkPreviewer::room() const
{
return m_currentRoom;
}
void LinkPreviewer::setRoom(NeoChatRoom *room)
{
if (room == m_currentRoom) {
return;
}
m_currentRoom = room;
Q_EMIT roomChanged();
} }
bool LinkPreviewer::loaded() const bool LinkPreviewer::loaded() const
@@ -57,13 +46,19 @@ QUrl LinkPreviewer::url() const
void LinkPreviewer::setUrl(QUrl url) void LinkPreviewer::setUrl(QUrl url)
{ {
if (url.scheme() == QStringLiteral("https")) { if (url != m_url) {
m_url = url;
urlChanged();
loadUrlPreview();
}
}
void LinkPreviewer::loadUrlPreview()
{
if (m_url.scheme() == QStringLiteral("https")) {
m_loaded = false; m_loaded = false;
Q_EMIT loadedChanged(); Q_EMIT loadedChanged();
m_url = url;
Q_EMIT urlChanged();
auto conn = m_currentRoom->connection(); auto conn = m_currentRoom->connection();
GetUrlPreviewJob *job = conn->callApi<GetUrlPreviewJob>(m_url.toString()); GetUrlPreviewJob *job = conn->callApi<GetUrlPreviewJob>(m_url.toString());

View File

@@ -19,12 +19,6 @@ class NeoChatRoom;
class LinkPreviewer : public QObject class LinkPreviewer : public QObject
{ {
Q_OBJECT Q_OBJECT
/**
* @brief The current room that the URL is from.
*/
Q_PROPERTY(NeoChatRoom *room READ room WRITE setRoom NOTIFY roomChanged)
/** /**
* @brief The URL to get the preview for. * @brief The URL to get the preview for.
*/ */
@@ -51,10 +45,7 @@ class LinkPreviewer : public QObject
Q_PROPERTY(QUrl imageSource READ imageSource NOTIFY imageSourceChanged) Q_PROPERTY(QUrl imageSource READ imageSource NOTIFY imageSourceChanged)
public: public:
explicit LinkPreviewer(QObject *parent = nullptr); explicit LinkPreviewer(QObject *parent = nullptr, NeoChatRoom *room = nullptr, QUrl url = {});
[[nodiscard]] NeoChatRoom *room() const;
void setRoom(NeoChatRoom *room);
[[nodiscard]] QUrl url() const; [[nodiscard]] QUrl url() const;
void setUrl(QUrl); void setUrl(QUrl);
@@ -67,16 +58,18 @@ private:
NeoChatRoom *m_currentRoom = nullptr; NeoChatRoom *m_currentRoom = nullptr;
bool m_loaded; bool m_loaded;
QString m_title; QString m_title = QString();
QString m_description; QString m_description = QString();
QUrl m_imageSource; QUrl m_imageSource = QUrl();
QUrl m_url; QUrl m_url;
void loadUrlPreview();
Q_SIGNALS: Q_SIGNALS:
void roomChanged();
void loadedChanged(); void loadedChanged();
void titleChanged(); void titleChanged();
void descriptionChanged(); void descriptionChanged();
void imageSourceChanged(); void imageSourceChanged();
void urlChanged(); void urlChanged();
}; };
Q_DECLARE_METATYPE(LinkPreviewer *)

View File

@@ -28,6 +28,7 @@
#include <KLocalizedString> #include <KLocalizedString>
#include "neochatuser.h" #include "neochatuser.h"
#include "texthandler.h"
using namespace Quotient; using namespace Quotient;
@@ -45,6 +46,8 @@ QHash<int, QByteArray> MessageEventModel::roleNames() const
roles[SpecialMarksRole] = "marks"; roles[SpecialMarksRole] = "marks";
roles[LongOperationRole] = "progressInfo"; roles[LongOperationRole] = "progressInfo";
roles[EventResolvedTypeRole] = "eventResolvedType"; roles[EventResolvedTypeRole] = "eventResolvedType";
roles[ShowLinkPreviewRole] = "showLinkPreview";
roles[LinkPreviewRole] = "linkPreview";
roles[MediaInfoRole] = "mediaInfo"; roles[MediaInfoRole] = "mediaInfo";
roles[IsReplyRole] = "isReply"; roles[IsReplyRole] = "isReply";
roles[ReplyAuthor] = "replyAuthor"; roles[ReplyAuthor] = "replyAuthor";
@@ -101,12 +104,20 @@ void MessageEventModel::setRoom(NeoChatRoom *room)
beginResetModel(); beginResetModel();
if (m_currentRoom) { if (m_currentRoom) {
m_currentRoom->disconnect(this); m_currentRoom->disconnect(this);
m_linkPreviewers.clear();
} }
m_currentRoom = room; m_currentRoom = room;
if (room) { if (room) {
m_lastReadEventIndex = QPersistentModelIndex(QModelIndex()); m_lastReadEventIndex = QPersistentModelIndex(QModelIndex());
room->setDisplayed(); room->setDisplayed();
for (auto event = m_currentRoom->messageEvents().begin(); event != m_currentRoom->messageEvents().end(); ++event) {
if (auto e = &*event->viewAs<RoomMessageEvent>()) {
createLinkPreviewerForEvent(e);
}
}
if (m_currentRoom->timelineSize() < 10 && !room->allHistoryLoaded()) { if (m_currentRoom->timelineSize() < 10 && !room->allHistoryLoaded()) {
room->getPreviousContent(50); room->getPreviousContent(50);
} }
@@ -118,10 +129,12 @@ void MessageEventModel::setRoom(NeoChatRoom *room)
using namespace Quotient; using namespace Quotient;
connect(m_currentRoom, &Room::aboutToAddNewMessages, this, [this](RoomEventsRange events) { connect(m_currentRoom, &Room::aboutToAddNewMessages, this, [this](RoomEventsRange events) {
if (NeoChatConfig::self()->showFancyEffects()) { for (auto &&event : events) {
for (auto &event : events) { const RoomMessageEvent *message = dynamic_cast<RoomMessageEvent *>(event.get());
RoomMessageEvent *message = dynamic_cast<RoomMessageEvent *>(event.get()); if (message != nullptr) {
if (message) { createLinkPreviewerForEvent(message);
if (NeoChatConfig::self()->showFancyEffects()) {
QString planBody = message->plainBody(); QString planBody = message->plainBody();
// snowflake // snowflake
const QString snowlakeEmoji = QString::fromUtf8("\xE2\x9D\x84"); const QString snowlakeEmoji = QString::fromUtf8("\xE2\x9D\x84");
@@ -155,6 +168,12 @@ void MessageEventModel::setRoom(NeoChatRoom *room)
beginInsertRows({}, timelineBaseIndex(), timelineBaseIndex() + int(events.size()) - 1); beginInsertRows({}, timelineBaseIndex(), timelineBaseIndex() + int(events.size()) - 1);
}); });
connect(m_currentRoom, &Room::aboutToAddHistoricalMessages, this, [this](RoomEventsRange events) { connect(m_currentRoom, &Room::aboutToAddHistoricalMessages, this, [this](RoomEventsRange events) {
for (auto &event : events) {
RoomMessageEvent *message = dynamic_cast<RoomMessageEvent *>(event.get());
if (message) {
createLinkPreviewerForEvent(message);
}
}
if (rowCount() > 0) { if (rowCount() > 0) {
rowBelowInserted = rowCount() - 1; // See #312 rowBelowInserted = rowCount() - 1; // See #312
} }
@@ -455,6 +474,8 @@ inline QVariantMap userInContext(NeoChatUser *user, NeoChatRoom *room)
}; };
} }
static LinkPreviewer *emptyLinkPreview = new LinkPreviewer;
QVariant MessageEventModel::data(const QModelIndex &idx, int role) const QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
{ {
const auto row = idx.row(); const auto row = idx.row();
@@ -684,6 +705,18 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
return role == TimeRole ? QVariant(ts) : renderDate(ts); return role == TimeRole ? QVariant(ts) : renderDate(ts);
} }
if (role == ShowLinkPreviewRole) {
return m_linkPreviewers.contains(evt.id());
}
if (role == LinkPreviewRole) {
if (m_linkPreviewers.contains(evt.id())) {
return QVariant::fromValue<LinkPreviewer *>(m_linkPreviewers[evt.id()]);
} else {
return QVariant::fromValue<LinkPreviewer *>(emptyLinkPreview);
}
}
if (role == MediaInfoRole) { if (role == MediaInfoRole) {
return getMediaInfoForEvent(evt); return getMediaInfoForEvent(evt);
} }
@@ -1197,3 +1230,29 @@ QVariantMap MessageEventModel::getMediaInfoFromFileInfo(const EventContent::File
return mediaInfo; return mediaInfo;
} }
void MessageEventModel::createLinkPreviewerForEvent(const Quotient::RoomMessageEvent *event)
{
if (m_linkPreviewers.contains(event->id())) {
return;
} else {
QString text;
if (event->hasTextContent()) {
auto textContent = static_cast<const EventContent::TextContent *>(event->content());
if (textContent) {
text = textContent->body;
} else {
text = event->plainBody();
}
} else {
text = event->plainBody();
}
TextHandler textHandler;
textHandler.setData(text);
QList<QUrl> links = textHandler.getLinkPreviews();
if (links.size() > 0) {
m_linkPreviewers[event->id()] = new LinkPreviewer(nullptr, m_currentRoom, links.size() > 0 ? links[0] : QUrl());
}
}
}

View File

@@ -5,6 +5,7 @@
#include <QAbstractListModel> #include <QAbstractListModel>
#include "linkpreviewer.h"
#include "neochatroom.h" #include "neochatroom.h"
/** /**
@@ -70,6 +71,9 @@ public:
FormattedBodyRole, /**< The formatted body of a rich message. */ FormattedBodyRole, /**< The formatted body of a rich message. */
GenericDisplayRole, /**< A generic string based upon the message type. */ GenericDisplayRole, /**< A generic string based upon the message type. */
ShowLinkPreviewRole, /**< Whether a link preview should be shown. */
LinkPreviewRole, /**< The link preview details. */
MediaInfoRole, /**< The media info for the event. */ MediaInfoRole, /**< The media info for the event. */
MimeTypeRole, /**< The mime type of the message's file or media. */ MimeTypeRole, /**< The mime type of the message's file or media. */
@@ -180,6 +184,8 @@ private:
int rowBelowInserted = -1; int rowBelowInserted = -1;
bool movingEvent = false; bool movingEvent = false;
QMap<QString, LinkPreviewer *> m_linkPreviewers;
[[nodiscard]] int timelineBaseIndex() const; [[nodiscard]] int timelineBaseIndex() const;
[[nodiscard]] QDateTime makeMessageTimestamp(const Quotient::Room::rev_iter_t &baseIt) const; [[nodiscard]] QDateTime makeMessageTimestamp(const Quotient::Room::rev_iter_t &baseIt) const;
[[nodiscard]] static QString renderDate(const QDateTime &timestamp); [[nodiscard]] static QString renderDate(const QDateTime &timestamp);
@@ -195,6 +201,7 @@ private:
const Quotient::RoomEvent *getReplyForEvent(const Quotient::RoomEvent &event) const; const Quotient::RoomEvent *getReplyForEvent(const Quotient::RoomEvent &event) const;
QVariantMap getMediaInfoForEvent(const Quotient::RoomEvent &event) const; QVariantMap getMediaInfoForEvent(const Quotient::RoomEvent &event) const;
QVariantMap getMediaInfoFromFileInfo(const Quotient::EventContent::FileInfo *fileInfo, const QString &eventId, bool isThumbnail = false) const; QVariantMap getMediaInfoFromFileInfo(const Quotient::EventContent::FileInfo *fileInfo, const QString &eventId, bool isThumbnail = false) const;
void createLinkPreviewerForEvent(const Quotient::RoomMessageEvent *event);
std::vector<Quotient::event_ptr_tt<Quotient::RoomEvent>> m_extraEvents; std::vector<Quotient::event_ptr_tt<Quotient::RoomEvent>> m_extraEvents;
// Hack to ensure that we don't call endInsertRows when we haven't called beginInsertRows // Hack to ensure that we don't call endInsertRows when we haven't called beginInsertRows

View File

@@ -14,33 +14,16 @@ Loader {
id: root id: root
/** /**
* @brief The room that the component is created in. * @brief The link preview properties.
*/
property var room
/**
* @brief Get a list of hyperlinks in the text.
* *
* User links i.e. anything starting with https://matrix.to are ignored. * This is a list or object containing the following:
* - url - The URL being previewed.
* - loaded - Whether the URL preview has been loaded.
* - title - the title of the URL preview.
* - description - the description of the URL preview.
* - imageSource - a source URL for the preview image.
*/ */
property var links: { property var linkPreviewer
let matches = model.display.match(/\bhttps?:\/\/[^\s\<\>\"\']+/g)
if (matches && matches.length > 0) {
// don't show previews for room links or user mentions or custom emojis
return matches.filter(link => !(
link.includes("https://matrix.to") || link.includes("/_matrix/media/r0/download/")
))
// remove ending fullstops and commas
.map(link => (link.length && [".", ","].includes(link[link.length-1])) ? link.substring(0, link.length-1) : link)
}
return []
}
LinkPreviewer {
id: linkPreviewer
room: root.room
url: root.links && root.links.length > 0 ? root.links[0] : ""
}
/** /**
* @brief Standard height for the link preview. * @brief Standard height for the link preview.
@@ -55,8 +38,7 @@ Loader {
*/ */
property bool indicatorEnabled: false property bool indicatorEnabled: false
active: !currentRoom.usesEncryption && model.display && links && links.length > 0 && currentRoom.urlPreviewEnabled visible: active
visible: Config.showLinkPreview && active
sourceComponent: linkPreviewer.loaded ? linkPreviewComponent : loadingComponent sourceComponent: linkPreviewer.loaded ? linkPreviewComponent : loadingComponent
Component { Component {
@@ -95,16 +77,16 @@ Loader {
wrapMode: Text.Wrap wrapMode: Text.Wrap
textFormat: Text.RichText textFormat: Text.RichText
text: "<style> text: "<style>
a { a {
text-decoration: none; text-decoration: none;
} }
</style> </style>
<a href=\"" + root.links[0] + "\">" + (maximizeButton.checked ? linkPreviewer.title : titleTextMetrics.elidedText).replace("&ndash;", "—") + "</a>" <a href=\"" + root.linkPreviewer.url + "\">" + (maximizeButton.checked ? root.linkPreviewer.title : titleTextMetrics.elidedText).replace("&ndash;", "—") + "</a>"
onLinkActivated: RoomManager.openResource(link) onLinkActivated: RoomManager.openResource(link)
TextMetrics { TextMetrics {
id: titleTextMetrics id: titleTextMetrics
text: linkPreviewer.title text: root.linkPreviewer.title
font: linkPreviewTitle.font font: linkPreviewTitle.font
elide: Text.ElideRight elide: Text.ElideRight
elideWidth: (linkPreviewTitle.width - Kirigami.Units.largeSpacing * 2.5) * 3 elideWidth: (linkPreviewTitle.width - Kirigami.Units.largeSpacing * 2.5) * 3

View File

@@ -35,7 +35,8 @@ TimelineContainer {
} }
LinkPreviewDelegate { LinkPreviewDelegate {
Layout.fillWidth: true Layout.fillWidth: true
room: currentRoom active: !currentRoom.usesEncryption && currentRoom.urlPreviewEnabled && Config.showLinkPreview && model.showLinkPreview
linkPreviewer: model.linkPreview
indicatorEnabled: messageDelegate.isVisibleInTimeline() indicatorEnabled: messageDelegate.isVisibleInTimeline()
} }
} }

View File

@@ -430,7 +430,21 @@ QString TextHandler::unescapeHtml(QString stringIn)
QString TextHandler::linkifyUrls(QString stringIn) QString TextHandler::linkifyUrls(QString stringIn)
{ {
stringIn = stringIn.replace(TextRegex::mxId, QStringLiteral(R"(\1<a href="https://matrix.to/#/\2">\2</a>)")); stringIn = stringIn.replace(TextRegex::mxId, QStringLiteral(R"(\1<a href="https://matrix.to/#/\2">\2</a>)"));
stringIn.replace(TextRegex::fullUrl, QStringLiteral(R"(<a href="\1">\1</a>)")); stringIn.replace(TextRegex::plainUrl, QStringLiteral(R"(<a href="\1">\1</a>)"));
stringIn = stringIn.replace(TextRegex::emailAddress, QStringLiteral(R"(<a href="mailto:\2">\1\2</a>)")); stringIn = stringIn.replace(TextRegex::emailAddress, QStringLiteral(R"(<a href="mailto:\2">\1\2</a>)"));
return stringIn; return stringIn;
} }
QList<QUrl> TextHandler::getLinkPreviews()
{
auto data = m_data.remove(TextRegex::removeRichReply);
auto linksMatch = TextRegex::url.globalMatch(data);
QList<QUrl> links;
while (linksMatch.hasNext()) {
auto link = linksMatch.next().captured();
if (!link.contains(QStringLiteral("matrix.to"))) {
links += QUrl(link);
}
}
return links;
}

View File

@@ -21,10 +21,13 @@ static const QRegularExpression codePill{QStringLiteral("<pre><code[^>]*>(.*?)</
static const QRegularExpression userPill{QStringLiteral("(<a href=\"https://matrix.to/#/@.*?:.*?\">.*?</a>)"), QRegularExpression::DotMatchesEverythingOption}; static const QRegularExpression userPill{QStringLiteral("(<a href=\"https://matrix.to/#/@.*?:.*?\">.*?</a>)"), QRegularExpression::DotMatchesEverythingOption};
static const QRegularExpression strikethrough{QStringLiteral("<del>(.*?)</del>"), QRegularExpression::DotMatchesEverythingOption}; static const QRegularExpression strikethrough{QStringLiteral("<del>(.*?)</del>"), QRegularExpression::DotMatchesEverythingOption};
static const QRegularExpression mxcImage{QStringLiteral(R"AAA(<img(.*?)src="mxc:\/\/(.*?)\/(.*?)"(.*?)>)AAA")}; static const QRegularExpression mxcImage{QStringLiteral(R"AAA(<img(.*?)src="mxc:\/\/(.*?)\/(.*?)"(.*?)>)AAA")};
static const QRegularExpression fullUrl( static const QRegularExpression plainUrl(
QStringLiteral( QStringLiteral(
R"(<a.*?<\/a>(*SKIP)(*F)|\b((www\.(?!\.)(?!(\w|\.|-)+@)|(https?|ftp):(//)?\w|(magnet|matrix):)(&(?![lg]t;)|[^&\s<>'"])+(&(?![lg]t;)|[^&!,.\s<>'"\]):])))"), R"(<a.*?<\/a>(*SKIP)(*F)|\b((www\.(?!\.)(?!(\w|\.|-)+@)|(https?|ftp):(//)?\w|(magnet|matrix):)(&(?![lg]t;)|[^&\s<>'"])+(&(?![lg]t;)|[^&!,.\s<>'"\]):])))"),
QRegularExpression::CaseInsensitiveOption | QRegularExpression::UseUnicodePropertiesOption); 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"(<a.*?<\/a>(*SKIP)(*F)|\b(mailto:)?((\w|\.|-)+@(\w|\.|-)+\.\w+\b))"), static const QRegularExpression emailAddress(QStringLiteral(R"(<a.*?<\/a>(*SKIP)(*F)|\b(mailto:)?((\w|\.|-)+@(\w|\.|-)+\.\w+\b))"),
QRegularExpression::CaseInsensitiveOption | QRegularExpression::UseUnicodePropertiesOption); QRegularExpression::CaseInsensitiveOption | QRegularExpression::UseUnicodePropertiesOption);
static const QRegularExpression mxId(QStringLiteral(R"((^|[][[:space:](){}`'";])([!#@][-a-z0-9_=#/.]{1,252}:\w(?:\w|\.|-)*\.\w+(?::\d{1,5})?))"), static const QRegularExpression mxId(QStringLiteral(R"((^|[][[:space:](){}`'";])([!#@][-a-z0-9_=#/.]{1,252}:\w(?:\w|\.|-)*\.\w+(?::\d{1,5})?))"),
@@ -109,6 +112,14 @@ public:
*/ */
QString handleRecievePlainText(Qt::TextFormat inputFormat = Qt::PlainText, const bool &stripNewlines = false); 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<QUrl> getLinkPreviews();
private: private:
QString m_data; QString m_data;