Link Previews

Uses Matrix's preview API to generate embedded link previews.

Only title and description for now.

![image](/uploads/2c5d632480073fe54345cdbe22ea54dc/image.png)
This commit is contained in:
Bharadwaj Raju
2022-09-22 15:42:53 +00:00
committed by Tobias Fella
parent 53b9f42399
commit 37780c2e3b
8 changed files with 196 additions and 4 deletions

View File

@@ -0,0 +1,64 @@
// SPDX-FileCopyrightText: 2022 Bharadwaj Raju <bharadwaj.raju777@protonmail.com>
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-or-later OR LicenseRef-KDE-Accepted-GPL
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import org.kde.kirigami 2.15 as Kirigami
import org.kde.neochat 1.0
RowLayout {
id: row
property var links: model.display.match(/(\bhttps?:\/\/[^\s\<\>\"\']*[^\s\<\>\"\'])/g)
// don't show previews for room links or user mentions
.filter(link => !link.includes("https://matrix.to"))
// remove ending fullstops and commas
.map(link => (link.length && [".", ","].includes(link[link.length-1])) ? link.substring(0, link.length-1) : link)
LinkPreviewer {
id: lp
url: links[0]
}
visible: lp.loaded && lp.title
Rectangle {
Layout.fillHeight: true
width: Kirigami.Units.smallSpacing
visible: lp.loaded && lp.title
color: Kirigami.Theme.highlightColor
}
Image {
visible: lp.imageSource
Layout.maximumHeight: Kirigami.Units.gridUnit * 5
Layout.maximumWidth: Kirigami.Units.gridUnit * 5
source: lp.imageSource.replace("mxc://", "image://mxc/")
fillMode: Image.PreserveAspectFit
}
ColumnLayout {
id: column
spacing: Kirigami.Units.smallSpacing
Kirigami.Heading {
Layout.maximumWidth: messageDelegate.bubbleMaxWidth
Layout.fillWidth: true
level: 4
wrapMode: Text.Wrap
textFormat: Text.RichText
text: "<style>
a {
text-decoration: none;
}
</style>
<a href=\"" + links[0] + "\">" + lp.title.replace("&ndash;", "—") + "</a>"
visible: lp.loaded
onLinkActivated: RoomManager.openResource(link)
}
Label {
text: lp.description
Layout.maximumWidth: messageDelegate.bubbleMaxWidth
Layout.fillWidth: true
wrapMode: Text.Wrap
visible: lp.loaded && lp.description
}
}
}

View File

@@ -19,9 +19,22 @@ TimelineContainer {
onReplyClicked: ListView.view.goToEvent(eventID)
hoverComponent: hoverActions
innerObject: RichLabel {
id: label
isEmote: messageDelegate.isEmote
Layout.maximumWidth: messageDelegate.bubbleMaxWidth
innerObject: ColumnLayout {
RichLabel {
id: label
isEmote: messageDelegate.isEmote
Layout.maximumWidth: messageDelegate.bubbleMaxWidth
}
Loader {
id: linkPreviewLoader
Layout.rightMargin: Kirigami.Units.largeSpacing
Layout.leftMargin: Kirigami.Units.largeSpacing
height: active ? item.implicitHeight : 0
active: !currentRoom.usesEncryption && model.display && model.display.includes("http")
visible: active
sourceComponent: LinkPreviewDelegate {
anchors.verticalCenter: parent.verticalCenter
}
}
}
}

View File

@@ -12,3 +12,4 @@ EncryptedDelegate 1.0 EncryptedDelegate.qml
EventDelegate 1.0 EventDelegate.qml
MessageDelegate 1.0 MessageDelegate.qml
ReadMarkerDelegate 1.0 ReadMarkerDelegate.qml
LinkPreviewDelegate 1.0 LinkPreviewDelegate.qml

View File

@@ -40,6 +40,7 @@
<file>imports/NeoChat/Component/Timeline/SectionDelegate.qml</file>
<file>imports/NeoChat/Component/Timeline/VideoDelegate.qml</file>
<file>imports/NeoChat/Component/Timeline/ReactionDelegate.qml</file>
<file>imports/NeoChat/Component/Timeline/LinkPreviewDelegate.qml</file>
<file>imports/NeoChat/Component/Timeline/AudioDelegate.qml</file>
<file>imports/NeoChat/Component/Timeline/FileDelegate.qml</file>
<file>imports/NeoChat/Component/Timeline/ImageDelegate.qml</file>

View File

@@ -39,6 +39,7 @@ add_library(neochat STATIC
collapsestateproxymodel.cpp
urlhelper.cpp
windowcontroller.cpp
linkpreviewer.cpp
)
add_executable(neochat-app

64
src/linkpreviewer.cpp Normal file
View File

@@ -0,0 +1,64 @@
// SPDX-FileCopyrightText: 2022 Bharadwaj Raju <bharadwaj.raju777@protonmail.com>
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-or-later OR LicenseRef-KDE-Accepted-GPL
#include "linkpreviewer.h"
#include "controller.h"
LinkPreviewer::LinkPreviewer(QObject *parent)
: QObject(parent)
, m_loaded(false)
{
}
bool LinkPreviewer::loaded() const
{
return m_loaded;
}
QString LinkPreviewer::title() const
{
return m_title;
}
QString LinkPreviewer::description() const
{
return m_description;
}
QString LinkPreviewer::imageSource() const
{
return m_imageSource;
}
QUrl LinkPreviewer::url() const
{
return m_url;
}
void LinkPreviewer::setUrl(QUrl url)
{
if (url.scheme() == QStringLiteral("https")) {
m_loaded = false;
Q_EMIT loadedChanged();
m_url = url;
Q_EMIT urlChanged();
auto conn = Controller::instance().activeConnection();
GetUrlPreviewJob *job = conn->callApi<GetUrlPreviewJob>(m_url.toString());
connect(job, &BaseJob::success, this, [this, job]() {
const auto json = job->jsonData();
m_title = json["og:title"].toString();
m_description = json["og:description"].toString();
m_imageSource = json["og:image"].toString();
m_loaded = true;
Q_EMIT titleChanged();
Q_EMIT descriptionChanged();
Q_EMIT imageSourceChanged();
Q_EMIT loadedChanged();
});
}
}

46
src/linkpreviewer.h Normal file
View File

@@ -0,0 +1,46 @@
// SPDX-FileCopyrightText: 2022 Bharadwaj Raju <bharadwaj.raju777@protonmail.com>
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-or-later OR LicenseRef-KDE-Accepted-GPL
#pragma once
#include <connection.h>
#include <csapi/content-repo.h>
#include <QObject>
using namespace Quotient;
class LinkPreviewer : public QObject
{
Q_OBJECT
Q_PROPERTY(bool loaded READ loaded NOTIFY loadedChanged)
Q_PROPERTY(QString title READ title NOTIFY titleChanged)
Q_PROPERTY(QString description READ description NOTIFY descriptionChanged)
Q_PROPERTY(QString imageSource READ imageSource NOTIFY imageSourceChanged)
Q_PROPERTY(QUrl url READ url WRITE setUrl NOTIFY urlChanged)
public:
explicit LinkPreviewer(QObject *parent = nullptr);
[[nodiscard]] bool loaded() const;
[[nodiscard]] QString title() const;
[[nodiscard]] QString description() const;
[[nodiscard]] QString imageSource() const;
[[nodiscard]] QUrl url() const;
void setUrl(QUrl);
private:
bool m_loaded;
QString m_title;
QString m_description;
QString m_imageSource;
QUrl m_url;
Q_SIGNALS:
void loadedChanged();
void titleChanged();
void descriptionChanged();
void imageSourceChanged();
void urlChanged();
};

View File

@@ -59,6 +59,7 @@
#include "emojimodel.h"
#include "filetypesingleton.h"
#include "joinrulesevent.h"
#include "linkpreviewer.h"
#include "login.h"
#include "matriximageprovider.h"
#include "messageeventmodel.h"
@@ -213,6 +214,7 @@ int main(int argc, char *argv[])
qmlRegisterType<SortFilterRoomListModel>("org.kde.neochat", 1, 0, "SortFilterRoomListModel");
qmlRegisterType<SortFilterSpaceListModel>("org.kde.neochat", 1, 0, "SortFilterSpaceListModel");
qmlRegisterType<DevicesModel>("org.kde.neochat", 1, 0, "DevicesModel");
qmlRegisterType<LinkPreviewer>("org.kde.neochat", 1, 0, "LinkPreviewer");
qmlRegisterUncreatableType<RoomMessageEvent>("org.kde.neochat", 1, 0, "RoomMessageEvent", "ENUM");
qmlRegisterUncreatableType<PushNotificationState>("org.kde.neochat", 1, 0, "PushNotificationState", "ENUM");
qmlRegisterUncreatableType<NeoChatRoomType>("org.kde.neochat", 1, 0, "NeoChatRoomType", "ENUM");