Link Previews
Uses Matrix's preview API to generate embedded link previews. Only title and description for now. 
This commit is contained in:
committed by
Tobias Fella
parent
53b9f42399
commit
37780c2e3b
64
imports/NeoChat/Component/Timeline/LinkPreviewDelegate.qml
Normal file
64
imports/NeoChat/Component/Timeline/LinkPreviewDelegate.qml
Normal 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("–", "—") + "</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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -19,9 +19,22 @@ TimelineContainer {
|
|||||||
onReplyClicked: ListView.view.goToEvent(eventID)
|
onReplyClicked: ListView.view.goToEvent(eventID)
|
||||||
hoverComponent: hoverActions
|
hoverComponent: hoverActions
|
||||||
|
|
||||||
innerObject: RichLabel {
|
innerObject: ColumnLayout {
|
||||||
id: label
|
RichLabel {
|
||||||
isEmote: messageDelegate.isEmote
|
id: label
|
||||||
Layout.maximumWidth: messageDelegate.bubbleMaxWidth
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,3 +12,4 @@ EncryptedDelegate 1.0 EncryptedDelegate.qml
|
|||||||
EventDelegate 1.0 EventDelegate.qml
|
EventDelegate 1.0 EventDelegate.qml
|
||||||
MessageDelegate 1.0 MessageDelegate.qml
|
MessageDelegate 1.0 MessageDelegate.qml
|
||||||
ReadMarkerDelegate 1.0 ReadMarkerDelegate.qml
|
ReadMarkerDelegate 1.0 ReadMarkerDelegate.qml
|
||||||
|
LinkPreviewDelegate 1.0 LinkPreviewDelegate.qml
|
||||||
|
|||||||
1
res.qrc
1
res.qrc
@@ -40,6 +40,7 @@
|
|||||||
<file>imports/NeoChat/Component/Timeline/SectionDelegate.qml</file>
|
<file>imports/NeoChat/Component/Timeline/SectionDelegate.qml</file>
|
||||||
<file>imports/NeoChat/Component/Timeline/VideoDelegate.qml</file>
|
<file>imports/NeoChat/Component/Timeline/VideoDelegate.qml</file>
|
||||||
<file>imports/NeoChat/Component/Timeline/ReactionDelegate.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/AudioDelegate.qml</file>
|
||||||
<file>imports/NeoChat/Component/Timeline/FileDelegate.qml</file>
|
<file>imports/NeoChat/Component/Timeline/FileDelegate.qml</file>
|
||||||
<file>imports/NeoChat/Component/Timeline/ImageDelegate.qml</file>
|
<file>imports/NeoChat/Component/Timeline/ImageDelegate.qml</file>
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ add_library(neochat STATIC
|
|||||||
collapsestateproxymodel.cpp
|
collapsestateproxymodel.cpp
|
||||||
urlhelper.cpp
|
urlhelper.cpp
|
||||||
windowcontroller.cpp
|
windowcontroller.cpp
|
||||||
|
linkpreviewer.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
add_executable(neochat-app
|
add_executable(neochat-app
|
||||||
|
|||||||
64
src/linkpreviewer.cpp
Normal file
64
src/linkpreviewer.cpp
Normal 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
46
src/linkpreviewer.h
Normal 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();
|
||||||
|
};
|
||||||
@@ -59,6 +59,7 @@
|
|||||||
#include "emojimodel.h"
|
#include "emojimodel.h"
|
||||||
#include "filetypesingleton.h"
|
#include "filetypesingleton.h"
|
||||||
#include "joinrulesevent.h"
|
#include "joinrulesevent.h"
|
||||||
|
#include "linkpreviewer.h"
|
||||||
#include "login.h"
|
#include "login.h"
|
||||||
#include "matriximageprovider.h"
|
#include "matriximageprovider.h"
|
||||||
#include "messageeventmodel.h"
|
#include "messageeventmodel.h"
|
||||||
@@ -213,6 +214,7 @@ int main(int argc, char *argv[])
|
|||||||
qmlRegisterType<SortFilterRoomListModel>("org.kde.neochat", 1, 0, "SortFilterRoomListModel");
|
qmlRegisterType<SortFilterRoomListModel>("org.kde.neochat", 1, 0, "SortFilterRoomListModel");
|
||||||
qmlRegisterType<SortFilterSpaceListModel>("org.kde.neochat", 1, 0, "SortFilterSpaceListModel");
|
qmlRegisterType<SortFilterSpaceListModel>("org.kde.neochat", 1, 0, "SortFilterSpaceListModel");
|
||||||
qmlRegisterType<DevicesModel>("org.kde.neochat", 1, 0, "DevicesModel");
|
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<RoomMessageEvent>("org.kde.neochat", 1, 0, "RoomMessageEvent", "ENUM");
|
||||||
qmlRegisterUncreatableType<PushNotificationState>("org.kde.neochat", 1, 0, "PushNotificationState", "ENUM");
|
qmlRegisterUncreatableType<PushNotificationState>("org.kde.neochat", 1, 0, "PushNotificationState", "ENUM");
|
||||||
qmlRegisterUncreatableType<NeoChatRoomType>("org.kde.neochat", 1, 0, "NeoChatRoomType", "ENUM");
|
qmlRegisterUncreatableType<NeoChatRoomType>("org.kde.neochat", 1, 0, "NeoChatRoomType", "ENUM");
|
||||||
|
|||||||
Reference in New Issue
Block a user