Improve Link Preview Sizing

Update linkpreviewer to minimise the number of resizes.

- There is now a placeholder loading element to hold space while the data is obtained
- The component starts with a set size but can be expanded to see the whole item.
- The expand/contract button is only visible on hover
- The default size is picked to show 3 lines of text (1 heading and 2 description, 2 heading and 1 description, etc)

This is designed to help with the timeline scroll jumping CCBUG: 463235 by minimizing the amount of resizing for items in the timeline.

Before

![image](/uploads/bb46f55dc38851e640a7382aafea8640/image.png)

After when compressed

![image](/uploads/3cbc7894688721d944a5551251b12a12/image.png)

After When expanded

![image](/uploads/50ff53fe51eefe88713a2ded34530534/image.png)

After Loading

![image](/uploads/72567f805af6b08ab0c5fc1cf94bd247/image.png)
This commit is contained in:
James Graham
2023-02-12 21:10:52 +00:00
parent 5482aad7ba
commit ea76edce74
2 changed files with 131 additions and 54 deletions

View File

@@ -1,4 +1,5 @@
// SPDX-FileCopyrightText: 2022 Bharadwaj Raju <bharadwaj.raju777@protonmail.com>
// SPDX-FileCopyrightText: 2023 James Graham <james.h.graham@protonmail.com>
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-or-later OR LicenseRef-KDE-Accepted-GPL
import QtQuick 2.15
@@ -9,58 +10,140 @@ import org.kde.kirigami 2.15 as Kirigami
import org.kde.neochat 1.0
RowLayout {
id: row
Loader {
id: root
property var links: model.display.match(/\bhttps?:\/\/[^\s\<\>\"\']+/g)
// don't show previews for room links or user mentions or custom emojis
.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)
/**
* @brief Get a list of hyperlinks in the text.
*
* User links i.e. anything starting with https://matrix.to are ignored.
*/
property var links: {
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: lp
url: links.length > 0 ? links[0] : ""
id: linkPreviewer
url: root.links && root.links.length > 0 ? root.links[0] : ""
}
visible: lp.loaded && lp.title
Rectangle {
Layout.fillHeight: true
width: Kirigami.Units.smallSpacing
visible: lp.loaded && lp.title
color: Kirigami.Theme.highlightColor
/**
* @brief Standard height for the link preview.
*
* When the content of the link preview is larger than this it will be
* elided/hidden until maximized.
*/
property var defaultHeight : Kirigami.Units.gridUnit * 3 + Kirigami.Units.smallSpacing * 2
active: !currentRoom.usesEncryption && model.display && links && links.length > 0
visible: Config.showLinkPreview && active
sourceComponent: linkPreviewer.loaded ? linkPreviewComponent : loadingComponent
Component {
id: linkPreviewComponent
QQC2.Control {
id: componentRoot
property bool truncated: linkPreviewDescription.truncated || !linkPreviewDescription.visible
leftPadding: 0
rightPadding: 0
topPadding: 0
bottomPadding: 0
contentItem: RowLayout {
spacing: Kirigami.Units.smallSpacing
Rectangle {
Layout.fillHeight: true
width: Kirigami.Units.smallSpacing
color: Kirigami.Theme.highlightColor
}
Image {
visible: linkPreviewer.imageSource
Layout.maximumHeight: root.defaultHeight
Layout.maximumWidth: root.defaultHeight
source: linkPreviewer.imageSource.replace("mxc://", "image://mxc/")
fillMode: Image.PreserveAspectFit
}
ColumnLayout {
id: column
spacing: Kirigami.Units.smallSpacing
Kirigami.Heading {
id: linkPreviewTitle
Layout.fillWidth: true
level: 3
wrapMode: Text.Wrap
textFormat: Text.RichText
text: "<style>
a {
text-decoration: none;
}
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)
</style>
<a href=\"" + root.links[0] + "\">" + (maximizeButton.checked ? linkPreviewer.title : titleTextMetrics.elidedText).replace("&ndash;", "—") + "</a>"
onLinkActivated: RoomManager.openResource(link)
TextMetrics {
id: titleTextMetrics
text: linkPreviewer.title
font: linkPreviewTitle.font
elide: Text.ElideRight
elideWidth: (linkPreviewTitle.width - Kirigami.Units.largeSpacing * 2.5) * 3
}
}
QQC2.Label {
id: linkPreviewDescription
Layout.fillWidth: true
Layout.maximumHeight: maximizeButton.checked ? -1 : root.defaultHeight - linkPreviewTitle.height - column.spacing
visible: linkPreviewTitle.height + column.spacing <= root.defaultHeight || maximizeButton.checked
text: linkPreviewer.description
wrapMode: Text.Wrap
elide: Text.ElideRight
}
}
}
QQC2.Button {
id: maximizeButton
anchors.right: parent.right
anchors.bottom: parent.bottom
visible: componentRoot.hovered && (componentRoot.truncated || checked)
checkable: true
icon.name: checked ? "go-up" : "go-down"
QQC2.ToolTip.text: checked ? i18n("Shrink preview") : i18n("Expand preview")
QQC2.ToolTip.visible: hovered
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
}
}
QQC2.Label {
text: lp.description
Layout.maximumWidth: messageDelegate.bubbleMaxWidth
Layout.fillWidth: true
wrapMode: Text.Wrap
visible: lp.loaded && lp.description
}
Component {
id: loadingComponent
RowLayout {
property bool hovered: false
property bool truncated: false
Rectangle {
Layout.fillHeight: true
width: Kirigami.Units.smallSpacing
color: Kirigami.Theme.highlightColor
}
QQC2.BusyIndicator { }
Kirigami.Heading {
Layout.fillWidth: true
Layout.minimumHeight: root.defaultHeight
level: 2
text: i18n("Loading URL preview")
}
}
}
}

View File

@@ -29,14 +29,8 @@ TimelineContainer {
messageId: model.eventId
visible: currentRoom.chatBoxEditId === model.eventId
}
Loader {
id: linkPreviewLoader
LinkPreviewDelegate {
Layout.fillWidth: true
active: !currentRoom.usesEncryption && model.display && model.display.includes("http")
visible: Config.showLinkPreview && active
sourceComponent: LinkPreviewDelegate {
anchors.verticalCenter: parent.verticalCenter
}
}
}
}