Multiple Link Previews
- Show a preview for each link in the text below the block in which it appears. - Allow link previews to be dismissed
This commit is contained in:
@@ -1,14 +0,0 @@
|
|||||||
{
|
|
||||||
"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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
{
|
|
||||||
"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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
{
|
|
||||||
"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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
{
|
|
||||||
"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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
{
|
|
||||||
"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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
{
|
|
||||||
"content": {
|
|
||||||
"body": "[Rich Link](https://kde.org)",
|
|
||||||
"format": "org.matrix.custom.html",
|
|
||||||
"formatted_body": "<a href=\"https://kde.org\">Rich Link</a>",
|
|
||||||
"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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -6,12 +6,11 @@
|
|||||||
|
|
||||||
#include "linkpreviewer.h"
|
#include "linkpreviewer.h"
|
||||||
|
|
||||||
|
#include "utils.h"
|
||||||
#include <Quotient/events/roommessageevent.h>
|
#include <Quotient/events/roommessageevent.h>
|
||||||
#include <Quotient/quotient_common.h>
|
#include <Quotient/quotient_common.h>
|
||||||
#include <Quotient/syncdata.h>
|
#include <Quotient/syncdata.h>
|
||||||
|
|
||||||
#include "utils.h"
|
|
||||||
|
|
||||||
#include "testutils.h"
|
#include "testutils.h"
|
||||||
|
|
||||||
using namespace Quotient;
|
using namespace Quotient;
|
||||||
@@ -30,6 +29,9 @@ private Q_SLOTS:
|
|||||||
void linkPreviewsMatch_data();
|
void linkPreviewsMatch_data();
|
||||||
void linkPreviewsMatch();
|
void linkPreviewsMatch();
|
||||||
|
|
||||||
|
void multipleLinkPreviewsMatch_data();
|
||||||
|
void multipleLinkPreviewsMatch();
|
||||||
|
|
||||||
void linkPreviewsReject_data();
|
void linkPreviewsReject_data();
|
||||||
void linkPreviewsReject();
|
void linkPreviewsReject();
|
||||||
};
|
};
|
||||||
@@ -42,45 +44,59 @@ void LinkPreviewerTest::initTestCase()
|
|||||||
|
|
||||||
void LinkPreviewerTest::linkPreviewsMatch_data()
|
void LinkPreviewerTest::linkPreviewsMatch_data()
|
||||||
{
|
{
|
||||||
QTest::addColumn<QString>("eventSource");
|
QTest::addColumn<QString>("inputString");
|
||||||
QTest::addColumn<QUrl>("testOutputLink");
|
QTest::addColumn<QUrl>("testOutputLink");
|
||||||
|
|
||||||
QTest::newRow("plainHttps") << QStringLiteral("test-validplainlink-event.json") << QUrl("https://kde.org"_ls);
|
QTest::newRow("plainHttps") << QStringLiteral("https://kde.org") << QUrl("https://kde.org"_ls);
|
||||||
QTest::newRow("richHttps") << QStringLiteral("test-validrichlink-event.json") << QUrl("https://kde.org"_ls);
|
QTest::newRow("richHttps") << QStringLiteral("<a href=\"https://kde.org\">Rich Link</a>") << QUrl("https://kde.org"_ls);
|
||||||
QTest::newRow("plainWww") << QStringLiteral("test-validplainwwwlink-event.json") << QUrl("www.example.org"_ls);
|
QTest::newRow("richHttpsLinkDescription") << QStringLiteral("<a href=\"https://kde.org\">https://kde.org</a>") << QUrl("https://kde.org"_ls);
|
||||||
QTest::newRow("multipleHttps") << QStringLiteral("test-multiplelink-event.json") << QUrl("www.example.org"_ls);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void LinkPreviewerTest::linkPreviewsMatch()
|
void LinkPreviewerTest::linkPreviewsMatch()
|
||||||
{
|
{
|
||||||
QFETCH(QString, eventSource);
|
QFETCH(QString, inputString);
|
||||||
QFETCH(QUrl, testOutputLink);
|
QFETCH(QUrl, testOutputLink);
|
||||||
|
|
||||||
auto event = TestUtils::loadEventFromFile<RoomMessageEvent>(eventSource);
|
auto link = LinkPreviewer::linkPreviews(inputString)[0];
|
||||||
auto linkPreviewer = LinkPreviewer(LinkPreviewer::linkPreview(event.get()), connection);
|
|
||||||
|
|
||||||
QCOMPARE(linkPreviewer.empty(), false);
|
QCOMPARE(link, testOutputLink);
|
||||||
QCOMPARE(linkPreviewer.url(), testOutputLink);
|
}
|
||||||
|
|
||||||
|
void LinkPreviewerTest::multipleLinkPreviewsMatch_data()
|
||||||
|
{
|
||||||
|
QTest::addColumn<QString>("inputString");
|
||||||
|
QTest::addColumn<QList<QUrl>>("testOutputLinks");
|
||||||
|
|
||||||
|
QTest::newRow("multipleHttps") << QStringLiteral("www.example.org https://kde.org") << QList{QUrl("www.example.org"_ls), QUrl("https://kde.org"_ls)};
|
||||||
|
QTest::newRow("multipleHttps1Invalid") << QStringLiteral("www.example.org mxc://example.org/SEsfnsuifSDFSSEF") << QList{QUrl("www.example.org"_ls)};
|
||||||
|
}
|
||||||
|
|
||||||
|
void LinkPreviewerTest::multipleLinkPreviewsMatch()
|
||||||
|
{
|
||||||
|
QFETCH(QString, inputString);
|
||||||
|
QFETCH(QList<QUrl>, testOutputLinks);
|
||||||
|
|
||||||
|
auto links = LinkPreviewer::linkPreviews(inputString);
|
||||||
|
|
||||||
|
QCOMPARE(links, testOutputLinks);
|
||||||
}
|
}
|
||||||
|
|
||||||
void LinkPreviewerTest::linkPreviewsReject_data()
|
void LinkPreviewerTest::linkPreviewsReject_data()
|
||||||
{
|
{
|
||||||
QTest::addColumn<QString>("eventSource");
|
QTest::addColumn<QString>("inputString");
|
||||||
|
|
||||||
QTest::newRow("mxc") << QStringLiteral("test-invalidmxclink-event.json");
|
QTest::newRow("mxc") << QStringLiteral("mxc://example.org/SEsfnsuifSDFSSEF");
|
||||||
QTest::newRow("matrixTo") << QStringLiteral("test-invalidmatrixtolink-event.json");
|
QTest::newRow("matrixTo") << QStringLiteral("https://matrix.to/#/@alice:example.org");
|
||||||
QTest::newRow("noSpace") << QStringLiteral("test-invalidnospacelink-event.json");
|
QTest::newRow("noSpace") << QStringLiteral("testhttps://kde.org");
|
||||||
}
|
}
|
||||||
|
|
||||||
void LinkPreviewerTest::linkPreviewsReject()
|
void LinkPreviewerTest::linkPreviewsReject()
|
||||||
{
|
{
|
||||||
QFETCH(QString, eventSource);
|
QFETCH(QString, inputString);
|
||||||
|
|
||||||
auto event = TestUtils::loadEventFromFile<RoomMessageEvent>(eventSource);
|
auto links = LinkPreviewer::linkPreviews(inputString);
|
||||||
auto linkPreviewer = LinkPreviewer(LinkPreviewer::linkPreview(event.get()), connection);
|
|
||||||
|
|
||||||
QCOMPARE(linkPreviewer.empty(), true);
|
QCOMPARE(links.empty(), true);
|
||||||
QCOMPARE(linkPreviewer.url(), QUrl());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QTEST_MAIN(LinkPreviewerTest)
|
QTEST_MAIN(LinkPreviewerTest)
|
||||||
|
|||||||
@@ -89,38 +89,23 @@ bool LinkPreviewer::empty() const
|
|||||||
return m_url.isEmpty();
|
return m_url.isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
QUrl LinkPreviewer::linkPreview(const Quotient::RoomMessageEvent *event)
|
QList<QUrl> LinkPreviewer::linkPreviews(QString string)
|
||||||
{
|
{
|
||||||
if (event == nullptr) {
|
auto data = string.remove(TextRegex::removeRichReply);
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
QString text;
|
|
||||||
if (event->hasTextContent()) {
|
|
||||||
auto textContent = static_cast<const Quotient::EventContent::TextContent *>(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);
|
auto linksMatch = TextRegex::url.globalMatch(data);
|
||||||
|
QList<QUrl> links;
|
||||||
while (linksMatch.hasNext()) {
|
while (linksMatch.hasNext()) {
|
||||||
auto link = linksMatch.next().captured();
|
auto link = linksMatch.next().captured();
|
||||||
if (!link.contains(QStringLiteral("matrix.to"))) {
|
if (!link.contains(QStringLiteral("matrix.to")) && !links.contains(QUrl(link))) {
|
||||||
return QUrl(link);
|
links += QUrl(link);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return {};
|
return links;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool LinkPreviewer::hasPreviewableLinks(const Quotient::RoomMessageEvent *event)
|
bool LinkPreviewer::hasPreviewableLinks(const QString &string)
|
||||||
{
|
{
|
||||||
return !linkPreview(event).isEmpty();
|
return !linkPreviews(string).isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
#include "moc_linkpreviewer.cpp"
|
#include "moc_linkpreviewer.cpp"
|
||||||
|
|||||||
@@ -7,11 +7,6 @@
|
|||||||
#include <QQmlEngine>
|
#include <QQmlEngine>
|
||||||
#include <QUrl>
|
#include <QUrl>
|
||||||
|
|
||||||
namespace Quotient
|
|
||||||
{
|
|
||||||
class RoomMessageEvent;
|
|
||||||
}
|
|
||||||
|
|
||||||
class NeoChatRoom;
|
class NeoChatRoom;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -71,19 +66,19 @@ public:
|
|||||||
[[nodiscard]] bool empty() const;
|
[[nodiscard]] bool empty() const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Whether the given event has at least 1 pre-viewable link.
|
* @brief Whether the given string has at least 1 pre-viewable link.
|
||||||
*
|
*
|
||||||
* A link is only pre-viewable if it is http, https or something starting with www.
|
* A link is only pre-viewable if it is http, https or something starting with www.
|
||||||
*/
|
*/
|
||||||
static bool hasPreviewableLinks(const Quotient::RoomMessageEvent *event);
|
static bool hasPreviewableLinks(const QString &string);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Return the link to be previewed from the given event.
|
* @brief Return previewable links from the given string.
|
||||||
*
|
*
|
||||||
* This function is designed to give only links that should be previewed so
|
* 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.
|
* http, https or something starting with www. The first valid link is returned.
|
||||||
*/
|
*/
|
||||||
static QUrl linkPreview(const Quotient::RoomMessageEvent *event);
|
static QList<QUrl> linkPreviews(QString string);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool m_loaded;
|
bool m_loaded;
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
#include <Quotient/events/stickerevent.h>
|
#include <Quotient/events/stickerevent.h>
|
||||||
|
|
||||||
#include <KLocalizedString>
|
#include <KLocalizedString>
|
||||||
|
#include <qlist.h>
|
||||||
|
|
||||||
#ifndef Q_OS_ANDROID
|
#ifndef Q_OS_ANDROID
|
||||||
#include <KSyntaxHighlighting/Definition>
|
#include <KSyntaxHighlighting/Definition>
|
||||||
@@ -110,11 +111,14 @@ MessageContentModel::MessageContentModel(const Quotient::RoomEvent *event, NeoCh
|
|||||||
endResetModel();
|
endResetModel();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
connect(m_room, &NeoChatRoom::urlPreviewEnabledChanged, this, &MessageContentModel::updateLinkPreviewer);
|
connect(m_room, &NeoChatRoom::urlPreviewEnabledChanged, this, [this]() {
|
||||||
connect(NeoChatConfig::self(), &NeoChatConfig::ShowLinkPreviewChanged, this, &MessageContentModel::updateLinkPreviewer);
|
updateComponents();
|
||||||
|
});
|
||||||
|
connect(NeoChatConfig::self(), &NeoChatConfig::ShowLinkPreviewChanged, this, [this]() {
|
||||||
|
updateComponents();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
updateLinkPreviewer();
|
|
||||||
updateComponents();
|
updateComponents();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -197,8 +201,9 @@ QVariant MessageContentModel::data(const QModelIndex &index, int role) const
|
|||||||
return eventHandler.getReplyMediaInfo();
|
return eventHandler.getReplyMediaInfo();
|
||||||
}
|
}
|
||||||
if (role == LinkPreviewerRole) {
|
if (role == LinkPreviewerRole) {
|
||||||
if (m_linkPreviewer != nullptr) {
|
if (component.type == MessageComponentType::LinkPreview) {
|
||||||
return QVariant::fromValue<LinkPreviewer *>(m_linkPreviewer);
|
return QVariant::fromValue<LinkPreviewer *>(
|
||||||
|
dynamic_cast<NeoChatConnection *>(m_room->connection())->previewerForLink(component.attributes["link"_ls].toUrl()));
|
||||||
} else {
|
} else {
|
||||||
return QVariant::fromValue<LinkPreviewer *>(emptyLinkPreview);
|
return QVariant::fromValue<LinkPreviewer *>(emptyLinkPreview);
|
||||||
}
|
}
|
||||||
@@ -272,13 +277,7 @@ void MessageContentModel::updateComponents(bool isEditing)
|
|||||||
m_components.append(componentsForType(eventHandler.messageComponentType()));
|
m_components.append(componentsForType(eventHandler.messageComponentType()));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_linkPreviewer != nullptr) {
|
addLinkPreviews();
|
||||||
if (m_linkPreviewer->loaded()) {
|
|
||||||
m_components += MessageComponent{MessageComponentType::LinkPreview, QString(), {}};
|
|
||||||
} else {
|
|
||||||
m_components += MessageComponent{MessageComponentType::LinkPreviewLoad, QString(), {}};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
endResetModel();
|
endResetModel();
|
||||||
}
|
}
|
||||||
@@ -326,41 +325,60 @@ QList<MessageComponent> MessageContentModel::componentsForType(MessageComponentT
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void MessageContentModel::updateLinkPreviewer()
|
MessageComponent MessageContentModel::linkPreviewComponent(const QUrl &link)
|
||||||
{
|
{
|
||||||
if (m_room == nullptr || m_event == nullptr) {
|
const auto linkPreviewer = dynamic_cast<NeoChatConnection *>(m_room->connection())->previewerForLink(link);
|
||||||
if (m_linkPreviewer != nullptr) {
|
if (linkPreviewer == nullptr) {
|
||||||
m_linkPreviewer->disconnect(this);
|
return {};
|
||||||
m_linkPreviewer = nullptr;
|
|
||||||
updateComponents();
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
if (!m_room->urlPreviewEnabled()) {
|
if (linkPreviewer->loaded()) {
|
||||||
if (m_linkPreviewer != nullptr) {
|
return MessageComponent{MessageComponentType::LinkPreview, QString(), {{"link"_ls, link}}};
|
||||||
m_linkPreviewer->disconnect(this);
|
} else {
|
||||||
m_linkPreviewer = nullptr;
|
connect(linkPreviewer, &LinkPreviewer::loadedChanged, [this, link]() {
|
||||||
updateComponents();
|
const auto linkPreviewer = dynamic_cast<NeoChatConnection *>(m_room->connection())->previewerForLink(link);
|
||||||
}
|
if (linkPreviewer != nullptr && linkPreviewer->loaded()) {
|
||||||
return;
|
for (auto &component : m_components) {
|
||||||
}
|
if (component.attributes["link"_ls].toUrl() == link) {
|
||||||
|
|
||||||
if (const auto event = eventCast<const Quotient::RoomMessageEvent>(m_event)) {
|
|
||||||
if (LinkPreviewer::hasPreviewableLinks(event)) {
|
|
||||||
m_linkPreviewer = dynamic_cast<NeoChatConnection *>(m_room->connection())->previewerForLink(LinkPreviewer::linkPreview(event));
|
|
||||||
updateComponents();
|
|
||||||
|
|
||||||
if (m_linkPreviewer != nullptr) {
|
|
||||||
connect(m_linkPreviewer, &LinkPreviewer::loadedChanged, [this]() {
|
|
||||||
if (m_linkPreviewer != nullptr && m_linkPreviewer->loaded()) {
|
|
||||||
// HACK: Because DelegateChooser can't switch the delegate on dataChanged it has to think there is a new delegate.
|
// HACK: Because DelegateChooser can't switch the delegate on dataChanged it has to think there is a new delegate.
|
||||||
beginResetModel();
|
beginResetModel();
|
||||||
m_components[m_components.size() - 1].type = MessageComponentType::LinkPreview;
|
component.type = MessageComponentType::LinkPreview;
|
||||||
endResetModel();
|
endResetModel();
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return MessageComponent{MessageComponentType::LinkPreviewLoad, QString(), {{"link"_ls, link}}};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MessageContentModel::addLinkPreviews()
|
||||||
|
{
|
||||||
|
int i = 0;
|
||||||
|
while (i < m_components.size()) {
|
||||||
|
const auto component = m_components.at(i);
|
||||||
|
if (component.type == MessageComponentType::Text || component.type == MessageComponentType::Quote) {
|
||||||
|
if (LinkPreviewer::hasPreviewableLinks(component.content)) {
|
||||||
|
const auto links = LinkPreviewer::linkPreviews(component.content);
|
||||||
|
for (qsizetype j = 0; j < links.size(); ++j) {
|
||||||
|
if (!m_removedLinkPreviews.contains(links[j])) {
|
||||||
|
m_components.insert(i + j + 1, linkPreviewComponent(links[j]));
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MessageContentModel::closeLinkPreview(int row)
|
||||||
|
{
|
||||||
|
if (m_components[row].type == MessageComponentType::LinkPreview || m_components[row].type == MessageComponentType::LinkPreviewLoad) {
|
||||||
|
beginResetModel();
|
||||||
|
m_removedLinkPreviews += m_components[row].attributes["link"_ls].toUrl();
|
||||||
|
m_components.remove(row);
|
||||||
|
m_components.squeeze();
|
||||||
|
updateComponents();
|
||||||
|
endResetModel();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -88,6 +88,13 @@ public:
|
|||||||
*/
|
*/
|
||||||
[[nodiscard]] QHash<int, QByteArray> roleNames() const override;
|
[[nodiscard]] QHash<int, QByteArray> roleNames() const override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Close the link preview at the given index.
|
||||||
|
*
|
||||||
|
* If the given index is not a link preview component, nothing happens.
|
||||||
|
*/
|
||||||
|
Q_INVOKABLE void closeLinkPreview(int row);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QPointer<NeoChatRoom> m_room;
|
QPointer<NeoChatRoom> m_room;
|
||||||
const Quotient::RoomEvent *m_event = nullptr;
|
const Quotient::RoomEvent *m_event = nullptr;
|
||||||
@@ -95,12 +102,14 @@ private:
|
|||||||
QList<MessageComponent> m_components;
|
QList<MessageComponent> m_components;
|
||||||
void updateComponents(bool isEditing = false);
|
void updateComponents(bool isEditing = false);
|
||||||
|
|
||||||
QPointer<LinkPreviewer> m_linkPreviewer;
|
|
||||||
ItineraryModel *m_itineraryModel = nullptr;
|
ItineraryModel *m_itineraryModel = nullptr;
|
||||||
|
|
||||||
QList<MessageComponent> componentsForType(MessageComponentType::Type type);
|
QList<MessageComponent> componentsForType(MessageComponentType::Type type);
|
||||||
|
MessageComponent linkPreviewComponent(const QUrl &link);
|
||||||
|
void addLinkPreviews();
|
||||||
|
|
||||||
|
QList<QUrl> m_removedLinkPreviews;
|
||||||
|
|
||||||
void updateLinkPreviewer();
|
|
||||||
void updateItineraryModel();
|
void updateItineraryModel();
|
||||||
bool m_emptyItinerary = false;
|
bool m_emptyItinerary = false;
|
||||||
|
|
||||||
|
|||||||
@@ -166,6 +166,7 @@ QQC2.Control {
|
|||||||
root.selectedTextChanged(selectedText);
|
root.selectedTextChanged(selectedText);
|
||||||
}
|
}
|
||||||
onShowMessageMenu: root.showMessageMenu()
|
onShowMessageMenu: root.showMessageMenu()
|
||||||
|
onRemoveLinkPreview: index => root.contentModel.closeLinkPreview(index)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,11 @@ import org.kde.kirigami as Kirigami
|
|||||||
QQC2.Control {
|
QQC2.Control {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The index of the delegate in the model.
|
||||||
|
*/
|
||||||
|
required property int index
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief The link preview properties.
|
* @brief The link preview properties.
|
||||||
*
|
*
|
||||||
@@ -32,7 +37,7 @@ QQC2.Control {
|
|||||||
* When the content of the link preview is larger than this it will be
|
* When the content of the link preview is larger than this it will be
|
||||||
* elided/hidden until maximized.
|
* elided/hidden until maximized.
|
||||||
*/
|
*/
|
||||||
property var defaultHeight: Kirigami.Units.gridUnit * 3 + Kirigami.Units.smallSpacing * 2
|
property var defaultHeight: Kirigami.Units.gridUnit * 3 + Kirigami.Units.largeSpacing * 2
|
||||||
|
|
||||||
property bool truncated: linkPreviewDescription.truncated || !linkPreviewDescription.visible
|
property bool truncated: linkPreviewDescription.truncated || !linkPreviewDescription.visible
|
||||||
|
|
||||||
@@ -41,6 +46,11 @@ QQC2.Control {
|
|||||||
*/
|
*/
|
||||||
property real maxContentWidth: -1
|
property real maxContentWidth: -1
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Request for this delegate to be removed.
|
||||||
|
*/
|
||||||
|
signal remove(int index)
|
||||||
|
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.maximumWidth: root.maxContentWidth
|
Layout.maximumWidth: root.maxContentWidth
|
||||||
|
|
||||||
@@ -110,6 +120,23 @@ QQC2.Control {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QQC2.Button {
|
||||||
|
id: closeButton
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.top: parent.top
|
||||||
|
visible: root.hovered
|
||||||
|
text: i18nc("As in remove the link preview so it's no longer shown", "Remove preview")
|
||||||
|
icon.name: "dialog-close"
|
||||||
|
display: QQC2.AbstractButton.IconOnly
|
||||||
|
|
||||||
|
onClicked: root.remove(root.index)
|
||||||
|
|
||||||
|
QQC2.ToolTip {
|
||||||
|
text: closeButton.text
|
||||||
|
visible: closeButton.hovered
|
||||||
|
delay: Kirigami.Units.toolTipDelay
|
||||||
|
}
|
||||||
|
}
|
||||||
QQC2.Button {
|
QQC2.Button {
|
||||||
id: maximizeButton
|
id: maximizeButton
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
@@ -122,7 +149,7 @@ QQC2.Control {
|
|||||||
|
|
||||||
QQC2.ToolTip {
|
QQC2.ToolTip {
|
||||||
text: maximizeButton.text
|
text: maximizeButton.text
|
||||||
visible: hovered
|
visible: maximizeButton.hovered
|
||||||
delay: Kirigami.Units.toolTipDelay
|
delay: Kirigami.Units.toolTipDelay
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,9 +10,14 @@ import org.kde.kirigami as Kirigami
|
|||||||
/**
|
/**
|
||||||
* @brief A component to show a link preview loading from a message.
|
* @brief A component to show a link preview loading from a message.
|
||||||
*/
|
*/
|
||||||
RowLayout {
|
QQC2.Control {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The index of the delegate in the model.
|
||||||
|
*/
|
||||||
|
required property int index
|
||||||
|
|
||||||
required property int type
|
required property int type
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -28,6 +33,11 @@ RowLayout {
|
|||||||
*/
|
*/
|
||||||
property real maxContentWidth: -1
|
property real maxContentWidth: -1
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Request for this delegate to be removed.
|
||||||
|
*/
|
||||||
|
signal remove(int index)
|
||||||
|
|
||||||
enum Type {
|
enum Type {
|
||||||
Reply,
|
Reply,
|
||||||
LinkPreview
|
LinkPreview
|
||||||
@@ -36,24 +46,46 @@ RowLayout {
|
|||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.maximumWidth: root.maxContentWidth
|
Layout.maximumWidth: root.maxContentWidth
|
||||||
|
|
||||||
Rectangle {
|
contentItem : RowLayout {
|
||||||
Layout.fillHeight: true
|
spacing: Kirigami.Units.smallSpacing
|
||||||
width: Kirigami.Units.smallSpacing
|
|
||||||
color: Kirigami.Theme.highlightColor
|
Rectangle {
|
||||||
}
|
Layout.fillHeight: true
|
||||||
QQC2.BusyIndicator {}
|
width: Kirigami.Units.smallSpacing
|
||||||
Kirigami.Heading {
|
color: Kirigami.Theme.highlightColor
|
||||||
Layout.fillWidth: true
|
}
|
||||||
Layout.minimumHeight: root.defaultHeight
|
QQC2.BusyIndicator {}
|
||||||
verticalAlignment: Text.AlignVCenter
|
Kirigami.Heading {
|
||||||
level: 2
|
Layout.fillWidth: true
|
||||||
text: {
|
Layout.minimumHeight: root.defaultHeight
|
||||||
switch (root.type) {
|
verticalAlignment: Text.AlignVCenter
|
||||||
case LoadComponent.Reply:
|
level: 2
|
||||||
return i18n("Loading reply");
|
text: {
|
||||||
case LoadComponent.LinkPreview:
|
switch (root.type) {
|
||||||
return i18n("Loading URL preview");
|
case LoadComponent.Reply:
|
||||||
|
return i18n("Loading reply");
|
||||||
|
case LoadComponent.LinkPreview:
|
||||||
|
return i18n("Loading URL preview");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QQC2.Button {
|
||||||
|
id: closeButton
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.top: parent.top
|
||||||
|
visible: root.hovered && root.type === LoadComponent.LinkPreview
|
||||||
|
text: i18nc("As in remove the link preview so it's no longer shown", "Remove preview")
|
||||||
|
icon.name: "dialog-close"
|
||||||
|
display: QQC2.AbstractButton.IconOnly
|
||||||
|
|
||||||
|
onClicked: root.remove(root.index)
|
||||||
|
|
||||||
|
QQC2.ToolTip {
|
||||||
|
text: closeButton.text
|
||||||
|
visible: closeButton.hovered
|
||||||
|
delay: Kirigami.Units.toolTipDelay
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -60,6 +60,8 @@ DelegateChooser {
|
|||||||
*/
|
*/
|
||||||
signal showMessageMenu
|
signal showMessageMenu
|
||||||
|
|
||||||
|
signal removeLinkPreview(int index)
|
||||||
|
|
||||||
role: "componentType"
|
role: "componentType"
|
||||||
|
|
||||||
DelegateChoice {
|
DelegateChoice {
|
||||||
@@ -199,6 +201,7 @@ DelegateChooser {
|
|||||||
roleValue: MessageComponentType.LinkPreview
|
roleValue: MessageComponentType.LinkPreview
|
||||||
delegate: LinkPreviewComponent {
|
delegate: LinkPreviewComponent {
|
||||||
maxContentWidth: root.maxContentWidth
|
maxContentWidth: root.maxContentWidth
|
||||||
|
onRemove: index => root.removeLinkPreview(index)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -207,6 +210,7 @@ DelegateChooser {
|
|||||||
delegate: LoadComponent {
|
delegate: LoadComponent {
|
||||||
type: LoadComponent.LinkPreview
|
type: LoadComponent.LinkPreview
|
||||||
maxContentWidth: root.maxContentWidth
|
maxContentWidth: root.maxContentWidth
|
||||||
|
onRemove: index => root.removeLinkPreview(index)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user