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:
committed by
Tobias Fella
parent
b82d3ab5ad
commit
72de7c6cfb
@@ -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"
|
||||||
|
|||||||
@@ -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());
|
||||||
|
|
||||||
|
|||||||
@@ -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 *)
|
||||||
|
|||||||
@@ -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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -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 ×tamp);
|
[[nodiscard]] static QString renderDate(const QDateTime ×tamp);
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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("–", "—") + "</a>"
|
<a href=\"" + root.linkPreviewer.url + "\">" + (maximizeButton.checked ? root.linkPreviewer.title : titleTextMetrics.elidedText).replace("–", "—") + "</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
|
||||||
|
|||||||
@@ -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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user