status-desktop/ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/LinksMessage.qml

375 lines
13 KiB
QML

import QtQuick 2.13
import QtGraphicalEffects 1.13
import QtQuick.Layouts 1.13
import utils 1.0
import "../../../../../shared"
import "../../../../../shared/status"
import "./" as MessageComponents
Column {
id: root
property string linkUrls: ""
property var container
property bool isCurrentUser: false
property bool isImageLink: false
readonly property string uuid: Utils.uuid()
spacing: Style.current.halfPadding
ListModel {
id: linksModel
Component.onCompleted: {
if (!root.linkUrls) {
return
}
root.linkUrls.split(" ").forEach(link => {
linksModel.append({link})
})
}
}
Repeater {
id: linksRepeater
model: linksModel // doesn't work with a JSON object model!
delegate: Loader {
id: linkMessageLoader
property bool fetched: false
property var linkData
property int linkWidth: linksRepeater.width
active: true
Connections {
target: appSettings
onWhitelistedUnfurlingSitesChanged: {
fetched = false
linkMessageLoader.sourceComponent = undefined
linkMessageLoader.sourceComponent = linkMessageLoader.getSourceComponent()
}
onNeverAskAboutUnfurlingAgainChanged: {
linkMessageLoader.sourceComponent = undefined
linkMessageLoader.sourceComponent = linkMessageLoader.getSourceComponent()
}
onDisplayChatImagesChanged: {
linkMessageLoader.sourceComponent = undefined
linkMessageLoader.sourceComponent = linkMessageLoader.getSourceComponent()
}
}
Connections {
id: linkFetchConnections
enabled: false
target: chatsModel
onLinkPreviewDataWasReceived: {
let response
try {
response = JSON.parse(previewData)
} catch (e) {
console.error(previewData, e)
return
}
if (response.uuid !== root.uuid) return
linkFetchConnections.enabled = false
if (!response.success) {
console.error(response.result.error)
return undefined
}
linkData = response.result
if (linkData.contentType.startsWith("image/")) {
return linkMessageLoader.sourceComponent = unfurledImageComponent
}
if (linkData.site && linkData.title) {
linkData.address = link
return linkMessageLoader.sourceComponent = unfurledLinkComponent
}
}
}
Connections {
id: linkCommunityFetchConnections
enabled: false
target: chatsModel.communities
onCommunityAdded: {
if (communityId !== linkData.communityId) {
return
}
linkCommunityFetchConnections.enabled = false
const data = Utils.getLinkDataForStatusLinks(link)
if (data) {
linkData = data
if (!data.fetching && data.communityId) {
return linkMessageLoader.sourceComponent = invitationBubble
}
return linkMessageLoader.sourceComponent = unfurledLinkComponent
}
}
}
function getSourceComponent() {
// Reset the height in case we set it to 0 below. See note below
// for more information
this.height = undefined
if (Utils.hasImageExtension(link)) {
if (appSettings.displayChatImages) {
linkData = {
thumbnailUrl: link
}
return unfurledImageComponent
} else {
if (isImageLink && index > 0) {
return
}
isImageLink = true
return enableLinkComponent
}
}
let linkWhiteListed = false
const linkHostname = Utils.getHostname(link)
const linkExists = Object.keys(appSettings.whitelistedUnfurlingSites).some(function(whitelistedHostname) {
const exists = linkHostname.endsWith(whitelistedHostname)
if (exists) {
linkWhiteListed = appSettings.whitelistedUnfurlingSites[whitelistedHostname] === true
}
return exists
})
if (!linkWhiteListed && linkExists && !appSettings.neverAskAboutUnfurlingAgain) {
return enableLinkComponent
}
if (linkWhiteListed) {
if (fetched) {
return
}
fetched = true
const data = Utils.getLinkDataForStatusLinks(link)
if (data) {
linkData = data
if (data.fetching && data.communityId) {
linkCommunityFetchConnections.enabled = true
return
}
if (data.communityId) {
return invitationBubble
}
return unfurledLinkComponent
}
linkFetchConnections.enabled = true
return chatsModel.getLinkPreviewData(link, root.uuid)
}
// setting the height to 0 allows the "enable link" dialog to
// disappear correctly when appSettings.neverAskAboutUnfurlingAgain
// is true. The height is reset at the top of this method.
this.height = 0
return undefined
}
Component.onCompleted: {
// putting this is onCompleted prevents automatic binding, where
// QML warns of a binding loop detected
this.sourceComponent = getSourceComponent()
}
}
}
Component {
id: unfurledImageComponent
MessageBorder {
width: linkImage.width
height: linkImage.height
isCurrentUser: root.isCurrentUser
StatusChatImageLoader {
id: linkImage
anchors.centerIn: parent
container: root.container
source: linkData.thumbnailUrl
imageWidth: 300
isCurrentUser: root.isCurrentUser
onClicked: imageClick(linkImage.imageAlias)
}
}
}
Component {
id: invitationBubble
InvitationBubble {
communityId: linkData.communityId
isLink: true
anchors.left: parent.left
}
}
Component {
id: unfurledLinkComponent
MessageBorder {
width: linkImage.width + 2
height: linkImage.height + (Style.current.smallPadding * 2) + linkTitle.height + 2 + linkSite.height
isCurrentUser: root.isCurrentUser
StatusChatImageLoader {
id: linkImage
container: root.container
source: linkData.thumbnailUrl
imageWidth: 300
isCurrentUser: root.isCurrentUser
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
anchors.topMargin: 1
}
StyledText {
id: linkTitle
text: linkData.title
font.pixelSize: 13
font.weight: Font.Medium
elide: Text.ElideRight
anchors.left: parent.left
anchors.right: parent.right
anchors.top: linkImage.bottom
anchors.rightMargin: Style.current.smallPadding
anchors.leftMargin: Style.current.smallPadding
anchors.topMargin: Style.current.smallPadding
}
StyledText {
id: linkSite
text: linkData.site
font.pixelSize: 12
font.weight: Font.Thin
color: Style.current.secondaryText
anchors.top: linkTitle.bottom
anchors.topMargin: 2
anchors.left: linkTitle.left
anchors.bottomMargin: Style.current.smallPadding
}
MouseArea {
anchors.top: linkImage.top
anchors.left: linkImage.left
anchors.right: linkImage.right
anchors.bottom: linkSite.bottom
cursorShape: Qt.PointingHandCursor
onClicked: {
if (!!linkData.callback) {
return linkData.callback()
}
appMain.openLink(linkData.address)
}
}
}
}
Component {
id: enableLinkComponent
Rectangle {
id: enableLinkRoot
width: 300
height: childrenRect.height + Style.current.smallPadding
radius: 16
border.width: 1
border.color: Style.current.border
color:Style.current.background
StatusIconButton {
icon.name: "close"
icon.width: 20
icon.height: 20
anchors.top: parent.top
anchors.topMargin: Style.current.smallPadding
anchors.right: parent.right
anchors.rightMargin: Style.current.smallPadding
onClicked: {
enableLinkRoot.height = 0
enableLinkRoot.visible = false
}
}
Image {
id: unfurlingImage
source: Style.png("unfurling-image")
width: 132
height: 94
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
anchors.topMargin: Style.current.smallPadding
}
StyledText {
id: enableText
//% "Enable automatic image unfurling"
text: isImageLink ? qsTrId("enable-automatic-image-unfurling") :
//% "Enable link previews in chat?"
qsTrId("enable-link-previews")
horizontalAlignment: Text.AlignHCenter
width: parent.width
wrapMode: Text.WordWrap
anchors.top: unfurlingImage.bottom
anchors.topMargin: Style.current.halfPadding
font.pixelSize: 15
}
StyledText {
id: infoText
//% "Once enabled, links posted in the chat may share your metadata with their owners"
text: qsTrId("once-enabled--links-posted-in-the-chat-may-share-your-metadata-with-their-owners")
horizontalAlignment: Text.AlignHCenter
width: parent.width
wrapMode: Text.WordWrap
anchors.top: enableText.bottom
font.pixelSize: 13
color: Style.current.secondaryText
}
Separator {
id: sep1
anchors.top: infoText.bottom
anchors.topMargin: Style.current.smallPadding
}
StatusButton {
id: enableBtn
//% "Enable in Settings"
text: qsTrId("enable-in-settings")
type: "secondary"
onClicked: {
appMain.changeAppSection(Constants.profile)
// TODO: replace with shared store constant
// Profile/RootStore.privacy_and_security_id
profileLayoutContainer.changeProfileSection(3)
}
width: parent.width
anchors.top: sep1.bottom
}
Separator {
id: sep2
anchors.top: enableBtn.bottom
anchors.topMargin: 0
}
StatusButton {
//% "Don't ask me again"
text: qsTrId("dont-ask")
type: "secondary"
onClicked: {
appSettings.neverAskAboutUnfurlingAgain = true
}
width: parent.width
anchors.top: sep2.bottom
}
}
}
}