import QtQuick 2.13 import QtGraphicalEffects 1.13 import QtQuick.Layouts 1.13 import utils 1.0 import StatusQ.Core 0.1 import StatusQ.Core.Theme 0.1 import StatusQ.Controls 0.1 import shared.panels 1.0 import shared.status 1.0 import "../controls" import "../panels" Column { id: root property var store property var container property string linkUrls: "" 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: localAccountSensitiveSettings 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: root.store.chatsModelInst 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: root.store.chatsModelInst.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 (localAccountSensitiveSettings.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(localAccountSensitiveSettings.whitelistedUnfurlingSites).some(function(whitelistedHostname) { const exists = linkHostname.endsWith(whitelistedHostname) if (exists) { linkWhiteListed = localAccountSensitiveSettings.whitelistedUnfurlingSites[whitelistedHostname] === true } return exists }) if (!linkWhiteListed && linkExists && !localAccountSensitiveSettings.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 root.store.chatsModelInst.getLinkPreviewData(link, root.uuid) } // setting the height to 0 allows the "enable link" dialog to // disappear correctly when localAccountSensitiveSettings.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 InvitationBubbleView { store: root.store 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 } StatusBaseText { 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 color: Theme.palette.directColor1 } StatusBaseText { id: linkSite text: linkData.site font.pixelSize: 12 font.weight: Font.Thin color: Theme.palette.baseColor1 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 StatusFlatRoundButton { anchors.top: parent.top anchors.topMargin: Style.current.smallPadding anchors.right: parent.right anchors.rightMargin: Style.current.smallPadding icon.width: 20 icon.height: 20 icon.name: "close-circle" 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 } StatusBaseText { 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 color: Theme.palette.directColor1 } StatusBaseText { 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: Theme.palette.baseColor1 } Separator { id: sep1 anchors.top: infoText.bottom anchors.topMargin: Style.current.smallPadding } StatusFlatButton { id: enableBtn //% "Enable in Settings" text: qsTrId("enable-in-settings") 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 } StatusFlatButton { //% "Don't ask me again" text: qsTrId("dont-ask") onClicked: { localAccountSensitiveSettings.neverAskAboutUnfurlingAgain = true } width: parent.width anchors.top: sep2.bottom } } } }