feat: whitelist gifs (no url extension needed)
Fixes #1377. Fixes #1479. Two sites have been added to the whitelist: giphy.com and tenor.com. `imageUrls` in its entirety has been removed and instead all links are being handle through the message `linkUrls`. This prevents double-handling of urls that may or may not be images. The logic to automatically show links previews works like this: 1. If the setting "display chat images" is enabled, all links that *contain* ".png", ".jpg", ".jpeg", ".svg", ".gif" will be automatically shown. If the URL doesn't contain the extension, we are not downloading it. This was meant to be somewhat of a security compromise as we do not want to download each and every link posted in a message just to find out its true content type. 2. If the above setting is *disabled*, then we follow the whitelist settings for tenor and giphy. This allows us to preview gifs that do not have a file extension in their url. feat: bump status-go to the commit that supports the new whitelist (https://github.com/status-im/status-go/pull/2094), and also lets us get link preview data from urls in the whitelist. NOTE: this commit was branched off status-go `develop`, so once it is merged, and we update this PR to the new commit, we will effectively be getting status-go develop changes. We *could* base that status-go PR off of master if it makes things easier. fix: height on settings update issue feat: move date/time of message below links fix: layout issues when changing setting `neverAskAboutUnfurlingAgain` feat: Add MessageBorder component to aid in showing rounded corners with different radius
This commit is contained in:
parent
810ce12a56
commit
6a0a75888b
|
@ -25,7 +25,6 @@ type
|
|||
ResponseTo = UserRole + 14
|
||||
PlainText = UserRole + 15
|
||||
Index = UserRole + 16
|
||||
ImageUrls = UserRole + 17
|
||||
Timeout = UserRole + 18
|
||||
Image = UserRole + 19
|
||||
Audio = UserRole + 20
|
||||
|
@ -133,7 +132,6 @@ QtObject:
|
|||
of ChatMessageRoles.OutgoingStatus: result = newQVariant(message.outgoingStatus)
|
||||
of ChatMessageRoles.ResponseTo: result = newQVariant(message.responseTo)
|
||||
of ChatMessageRoles.Index: result = newQVariant(index.row)
|
||||
of ChatMessageRoles.ImageUrls: result = newQVariant(message.imageUrls)
|
||||
of ChatMessageRoles.Timeout: result = newQVariant(self.timedoutMessages.contains(message.id))
|
||||
of ChatMessageRoles.Image: result = newQVariant(message.image)
|
||||
of ChatMessageRoles.Audio: result = newQVariant(message.audio)
|
||||
|
@ -172,7 +170,6 @@ QtObject:
|
|||
ChatMessageRoles.OutgoingStatus.int: "outgoingStatus",
|
||||
ChatMessageRoles.ResponseTo.int: "responseTo",
|
||||
ChatMessageRoles.Index.int: "index",
|
||||
ChatMessageRoles.ImageUrls.int: "imageUrls",
|
||||
ChatMessageRoles.Timeout.int: "timeout",
|
||||
ChatMessageRoles.Image.int: "image",
|
||||
ChatMessageRoles.Audio.int: "audio",
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import NimQml, sequtils, strutils, sugar, os, json, chronicles
|
||||
import views/[mailservers_list, ens_manager, contacts, devices, mailservers, mnemonic, network, fleets, profile_info, device_list, dapp_list]
|
||||
import chronicles
|
||||
import ../chat/views/channels_list
|
||||
import ../../status/profile/profile
|
||||
import ../../status/profile as status_profile
|
||||
|
@ -15,6 +16,11 @@ import ../../status/libstatus/accounts/constants as accountConstants
|
|||
import qrcode/qrcode
|
||||
import ../utils/image_utils
|
||||
|
||||
logScope:
|
||||
topics = "profile-view"
|
||||
|
||||
const UNKNOWN_ACCOUNT = "unknownAccount"
|
||||
|
||||
QtObject:
|
||||
type ProfileView* = ref object of QObject
|
||||
profile*: ProfileInfoView
|
||||
|
@ -71,7 +77,7 @@ QtObject:
|
|||
proc getProfileSettingsFile(self: ProfileView): string {.slot.} =
|
||||
let address =
|
||||
if (self.profile.address == ""):
|
||||
"unknownAccount"
|
||||
UNKNOWN_ACCOUNT
|
||||
else:
|
||||
self.profile.address
|
||||
|
||||
|
@ -90,6 +96,12 @@ QtObject:
|
|||
self.profile.setProfile(profile)
|
||||
self.profileChanged()
|
||||
self.profileSettingsFileChanged()
|
||||
# Remove old 'unknownAccount' settings file if it was created
|
||||
let unknownSettingsPath = os.joinPath(accountConstants.DATADIR, "qt", UNKNOWN_ACCOUNT)
|
||||
if (not unknownSettingsPath.tryRemoveFile):
|
||||
# Only fails if the file exists and an there was an error removing it
|
||||
# More info: https://nim-lang.org/docs/os.html#tryRemoveFile%2Cstring
|
||||
warn "Failed to remove unused settings file", file=unknownSettingsPath
|
||||
|
||||
QtProperty[QVariant] profile:
|
||||
read = getProfile
|
||||
|
|
|
@ -57,7 +57,6 @@ type Message* = object
|
|||
isCurrentUser*: bool
|
||||
stickerHash*: string
|
||||
outgoingStatus*: string
|
||||
imageUrls*: string
|
||||
linkUrls*: string
|
||||
image*: string
|
||||
audio*: string
|
||||
|
|
|
@ -198,7 +198,6 @@ proc toMessage*(jsonMsg: JsonNode): Message =
|
|||
isCurrentUser: $jsonMsg{"outgoingStatus"}.getStr == "sending" or $jsonMsg{"outgoingStatus"}.getStr == "sent",
|
||||
stickerHash: "",
|
||||
parsedText: @[],
|
||||
imageUrls: "",
|
||||
linkUrls: "",
|
||||
image: $jsonMsg{"image"}.getStr,
|
||||
audio: $jsonMsg{"audio"}.getStr,
|
||||
|
@ -210,12 +209,6 @@ proc toMessage*(jsonMsg: JsonNode): Message =
|
|||
for text in jsonMsg["parsedText"]:
|
||||
message.parsedText.add(text.toTextItem)
|
||||
|
||||
message.imageUrls = concat(message.parsedText.map(t => t.children.filter(c => c.textType == "link")))
|
||||
.filter(t => [".png", ".jpg", ".jpeg", ".svg", ".gif"].any(ext => t.destination.endsWith(ext)))
|
||||
.map(t => t.destination)
|
||||
.join(" ")
|
||||
|
||||
|
||||
message.linkUrls = concat(message.parsedText.map(t => t.children.filter(c => c.textType == "link")))
|
||||
.filter(t => t.destination.startsWith("http"))
|
||||
.map(t => t.destination)
|
||||
|
|
|
@ -300,7 +300,6 @@ ScrollView {
|
|||
messageId: model.messageId
|
||||
emojiReactions: model.emojiReactions
|
||||
linkUrls: model.linkUrls
|
||||
imageUrls: model.imageUrls
|
||||
prevMessageIndex: {
|
||||
// This is used in order to have access to the previous message and determine the timestamp
|
||||
// we can't rely on the index because the sequence of messages is not ordered on the nim side
|
||||
|
|
|
@ -24,7 +24,6 @@ Item {
|
|||
property int prevMessageIndex: -1
|
||||
property bool timeout: false
|
||||
property string linkUrls: ""
|
||||
property string imageUrls: ""
|
||||
property bool placeholderMessage: false
|
||||
|
||||
property string authorCurrentMsg: "authorCurrentMsg"
|
||||
|
@ -226,7 +225,6 @@ Item {
|
|||
NormalMessage {
|
||||
clickMessage: root.clickMessage
|
||||
linkUrls: root.linkUrls
|
||||
imageUrls: root.imageUrls
|
||||
isCurrentUser: root.isCurrentUser
|
||||
contentType: root.contentType
|
||||
container: root
|
||||
|
@ -247,7 +245,7 @@ Item {
|
|||
CompactMessage {
|
||||
clickMessage: root.clickMessage
|
||||
linkUrls: root.linkUrls
|
||||
imageUrls: root.imageUrls
|
||||
isCurrentUser: root.isCurrentUser
|
||||
contentType: root.contentType
|
||||
container: root
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ Loader {
|
|||
property bool longReply: false
|
||||
property color elementsColor: isCurrentUser ? Style.current.chatReplyCurrentUser : Style.current.secondaryText
|
||||
property var container
|
||||
property int chatHorizontalPadding
|
||||
|
||||
id: root
|
||||
active: responseTo != "" && replyMessageIndex > -1
|
||||
|
|
|
@ -6,11 +6,10 @@ Item {
|
|||
property var clickMessage: function () {}
|
||||
property int chatHorizontalPadding: 12
|
||||
property int chatVerticalPadding: 7
|
||||
property string imageUrls: ""
|
||||
property bool showImages: appSettings.displayChatImages && root.imageUrls != ""
|
||||
property string linkUrls: ""
|
||||
property int contentType: 2
|
||||
property var container
|
||||
property bool isCurrentUser: false
|
||||
|
||||
id: root
|
||||
anchors.top: parent.top
|
||||
|
@ -49,6 +48,7 @@ Item {
|
|||
anchors.right: parent.right
|
||||
anchors.rightMargin: root.chatHorizontalPadding
|
||||
container: root.container
|
||||
chatHorizontalPadding: root.chatHorizontalPadding
|
||||
}
|
||||
|
||||
ChatText {
|
||||
|
@ -136,26 +136,6 @@ Item {
|
|||
anchors.rightMargin: 5
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: imageLoader
|
||||
active: root.showImages
|
||||
anchors.left: chatText.left
|
||||
anchors.leftMargin: 8
|
||||
anchors.top: chatText.bottom
|
||||
|
||||
sourceComponent: Component {
|
||||
ImageMessage {
|
||||
color: Style.current.transparent
|
||||
chatHorizontalPadding: 0
|
||||
imageUrls: root.imageUrls
|
||||
onClicked: {
|
||||
root.clickMessage(false, false, true, image)
|
||||
}
|
||||
container: root.container
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: linksLoader
|
||||
active: !!root.linkUrls
|
||||
|
@ -166,6 +146,8 @@ Item {
|
|||
sourceComponent: Component {
|
||||
LinksMessage {
|
||||
linkUrls: root.linkUrls
|
||||
container: root.container
|
||||
isCurrentUser: root.isCurrentUser
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ Item {
|
|||
property bool isCurrentUser: false
|
||||
property url source
|
||||
property bool playing: true
|
||||
property bool isAnimated: !!source && source.toString().endsWith('.gif')
|
||||
property bool isAnimated: true
|
||||
signal clicked(var image)
|
||||
property var container
|
||||
|
||||
|
|
|
@ -1,59 +0,0 @@
|
|||
import QtQuick 2.3
|
||||
import "../../../../../imports"
|
||||
|
||||
Rectangle {
|
||||
property int chatVerticalPadding: 12
|
||||
property int chatHorizontalPadding: 12
|
||||
property bool isCurrentUser: false
|
||||
signal clicked(var image)
|
||||
property var container
|
||||
property string imageUrls: ""
|
||||
|
||||
id: imageChatBox
|
||||
height: {
|
||||
let h = appSettings.compactMode ? 0 : chatVerticalPadding
|
||||
for (let i = 0; i < imageRepeater.count; i++) {
|
||||
h += imageRepeater.itemAt(i).height
|
||||
}
|
||||
return h + chatVerticalPadding * imageRepeater.count
|
||||
}
|
||||
color: "transparent"
|
||||
border.color: "transparent"
|
||||
width: {
|
||||
let w = 0
|
||||
for (let i = 0; i < imageRepeater.count; i++) {
|
||||
if (imageRepeater.itemAt(i).width > w) {
|
||||
w = imageRepeater.itemAt(i).width
|
||||
}
|
||||
}
|
||||
return w + 2 * chatHorizontalPadding
|
||||
}
|
||||
|
||||
radius: 16
|
||||
|
||||
Repeater {
|
||||
id: imageRepeater
|
||||
model: {
|
||||
if (!root.imageUrls) {
|
||||
return []
|
||||
}
|
||||
|
||||
return root.imageUrls.split(" ")
|
||||
}
|
||||
|
||||
ImageLoader {
|
||||
verticalPadding: imageChatBox.chatVerticalPadding
|
||||
anchors.top: (index === 0) ? parent.top: parent.children[index-1].bottom
|
||||
anchors.topMargin: verticalPadding
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
source: modelData
|
||||
isCurrentUser: imageChatBox.isCurrentUser
|
||||
onClicked: {
|
||||
imageChatBox.clicked(image)
|
||||
}
|
||||
container: imageChatBox.container
|
||||
}
|
||||
}
|
||||
|
||||
RectangleCorner {}
|
||||
}
|
|
@ -1,172 +1,186 @@
|
|||
import QtQuick 2.3
|
||||
import QtQuick 2.13
|
||||
import QtGraphicalEffects 1.13
|
||||
import QtQuick.Layouts 1.13
|
||||
import "../../../../../imports"
|
||||
import "../../../../../shared"
|
||||
import "../../../../../shared/status"
|
||||
import "./" as MessageComponents
|
||||
import "../../../Profile/LeftTab/constants.js" as ProfileConstants
|
||||
|
||||
Item {
|
||||
Column {
|
||||
id: root
|
||||
property string linkUrls: ""
|
||||
property var container
|
||||
property bool isCurrentUser: false
|
||||
spacing: Style.current.halfPadding
|
||||
|
||||
height: {
|
||||
let h = 0
|
||||
for (let i = 0; i < linksRepeater.count; i++) {
|
||||
h += linksRepeater.itemAt(i).height
|
||||
}
|
||||
return h
|
||||
}
|
||||
width: {
|
||||
let w = 0
|
||||
for (let i = 0; i < linksRepeater.count; i++) {
|
||||
if (linksRepeater.itemAt(i).width > w) {
|
||||
w = linksRepeater.itemAt(i).width
|
||||
ListModel {
|
||||
id: linksModel
|
||||
Component.onCompleted: {
|
||||
if (!root.linkUrls) {
|
||||
return
|
||||
}
|
||||
root.linkUrls.split(" ").forEach(link => {
|
||||
linksModel.append({link})
|
||||
})
|
||||
}
|
||||
return w
|
||||
}
|
||||
|
||||
Repeater {
|
||||
id: linksRepeater
|
||||
model: {
|
||||
if (!root.linkUrls) {
|
||||
return []
|
||||
}
|
||||
|
||||
return root.linkUrls.split(" ")
|
||||
}
|
||||
|
||||
model: linksModel // doesn't work with a JSON object model!
|
||||
|
||||
delegate: Loader {
|
||||
property string linkString: modelData
|
||||
|
||||
// This connection is needed because since the white list is an array, when something in it changes,
|
||||
// The whole object is still the same (reference), so the normal signal is not sent
|
||||
id: linkMessageLoader
|
||||
property var linkData
|
||||
property int linkWidth: linksRepeater.width
|
||||
active: true
|
||||
|
||||
Connections {
|
||||
target: applicationWindow
|
||||
onWhitelistChanged: {
|
||||
target: appSettings
|
||||
onWhitelistedUnfurlingSitesChanged: {
|
||||
linkMessageLoader.sourceComponent = undefined
|
||||
linkMessageLoader.sourceComponent = linkMessageLoader.getSourceComponent()
|
||||
}
|
||||
onNeverAskAboutUnfurlingAgainChanged: {
|
||||
linkMessageLoader.sourceComponent = undefined
|
||||
linkMessageLoader.sourceComponent = linkMessageLoader.getSourceComponent()
|
||||
}
|
||||
onDisplayChatImagesChanged: {
|
||||
linkMessageLoader.sourceComponent = undefined
|
||||
linkMessageLoader.sourceComponent = linkMessageLoader.getSourceComponent()
|
||||
}
|
||||
}
|
||||
|
||||
function getSourceComponent() {
|
||||
let linkExists = false
|
||||
let linkWhiteListed = false
|
||||
Object.keys(appSettings.whitelistedUnfurlingSites).some(function (site) {
|
||||
// Check if our link contains the string part of the url
|
||||
// TODO this might become not a reliable way to check since youtube has mutliple ways of being shown
|
||||
if (modelData.includes(site)) {
|
||||
linkExists = true
|
||||
// check if it was enabled
|
||||
linkWhiteListed = appSettings.whitelistedUnfurlingSites[site] === true
|
||||
return true
|
||||
// Reset the height in case we set it to 0 below. See note below
|
||||
// for more information
|
||||
this.height = undefined
|
||||
if (appSettings.displayChatImages && Utils.hasImageExtension(link)) {
|
||||
linkData = {
|
||||
thumbnailUrl: link
|
||||
}
|
||||
return
|
||||
})
|
||||
|
||||
if (linkWhiteListed) {
|
||||
return unfurledLinkComponent
|
||||
return unfurledImageComponent
|
||||
}
|
||||
if (linkExists && !appSettings.neverAskAboutUnfurlingAgain) {
|
||||
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
|
||||
}
|
||||
|
||||
return
|
||||
if (linkWhiteListed) {
|
||||
const data = chatsModel.getLinkPreviewData(link)
|
||||
linkData = JSON.parse(data)
|
||||
if (linkData.error) {
|
||||
console.error(linkData.error)
|
||||
return undefined
|
||||
}
|
||||
if (linkData.contentType.startsWith("image/")) {
|
||||
return unfurledImageComponent
|
||||
}
|
||||
if (linkData.site && linkData.title) {
|
||||
linkData.address = link
|
||||
return unfurledLinkComponent
|
||||
}
|
||||
}
|
||||
// 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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
id: linkMessageLoader
|
||||
active: true
|
||||
sourceComponent: getSourceComponent()
|
||||
Component {
|
||||
id: unfurledImageComponent
|
||||
|
||||
MessageBorder {
|
||||
width: linkImage.width
|
||||
height: linkImage.height
|
||||
isCurrentUser: root.isCurrentUser
|
||||
MessageComponents.ImageLoader {
|
||||
id: linkImage
|
||||
anchors.centerIn: parent
|
||||
container: root.container
|
||||
source: linkData.thumbnailUrl
|
||||
imageWidth: 300
|
||||
isCurrentUser: root.isCurrentUser
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: unfurledLinkComponent
|
||||
Loader {
|
||||
property var linkData: {
|
||||
const data = chatsModel.getLinkPreviewData(linkString)
|
||||
const result = JSON.parse(data)
|
||||
if (result.error) {
|
||||
console.error(result.error)
|
||||
return undefined
|
||||
}
|
||||
return result
|
||||
MessageBorder {
|
||||
width: linkImage.width + 2
|
||||
height: linkImage.height + (Style.current.smallPadding * 2) + linkTitle.height + 2 + linkSite.height
|
||||
isCurrentUser: root.isCurrentUser
|
||||
|
||||
MessageComponents.ImageLoader {
|
||||
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
|
||||
}
|
||||
active: linkData !== undefined && !!linkData.title
|
||||
sourceComponent: Component {
|
||||
Rectangle {
|
||||
id: rectangle
|
||||
width: 300
|
||||
height: childrenRect.height + Style.current.halfPadding
|
||||
radius: 16
|
||||
clip: true
|
||||
border.width: 1
|
||||
border.color: Style.current.border
|
||||
color:Style.current.background
|
||||
|
||||
// TODO the clip doesnt seem to work. Find another way to have rounded corners and wait for designs
|
||||
Image {
|
||||
id: linkImage
|
||||
source: linkData.thumbnailUrl
|
||||
fillMode: Image.PreserveAspectFit
|
||||
width: parent.width
|
||||
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
|
||||
}
|
||||
|
||||
layer.enabled: true
|
||||
layer.effect: OpacityMask {
|
||||
maskSource: Item {
|
||||
width: linkImage.width
|
||||
height: linkImage.height
|
||||
Rectangle {
|
||||
anchors.centerIn: parent
|
||||
width: linkImage.width
|
||||
height: linkImage.height
|
||||
radius: 16
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.top: linkImage.top
|
||||
anchors.left: linkImage.left
|
||||
anchors.right: linkImage.right
|
||||
anchors.bottom: linkSite.bottom
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: Qt.openUrlExternally(linkString)
|
||||
}
|
||||
}
|
||||
MouseArea {
|
||||
anchors.top: linkImage.top
|
||||
anchors.left: linkImage.left
|
||||
anchors.right: linkImage.right
|
||||
anchors.bottom: linkSite.bottom
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: Qt.openUrlExternally(linkData.address)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
Component {
|
||||
id: enableLinkComponent
|
||||
Rectangle {
|
||||
id: enableLinkRoot
|
||||
width: 300
|
||||
height: childrenRect.height + Style.current.smallPadding
|
||||
radius: 16
|
||||
|
@ -182,6 +196,10 @@ Item {
|
|||
anchors.topMargin: Style.current.smallPadding
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: Style.current.smallPadding
|
||||
onClicked: {
|
||||
enableLinkRoot.height = 0
|
||||
enableLinkRoot.visible = false
|
||||
}
|
||||
}
|
||||
|
||||
Image {
|
||||
|
@ -231,7 +249,6 @@ Item {
|
|||
profileLayoutContainer.changeProfileSection(ProfileConstants.PRIVACY_AND_SECURITY)
|
||||
}
|
||||
width: parent.width
|
||||
// height: 43
|
||||
anchors.top: sep1.bottom
|
||||
}
|
||||
|
||||
|
@ -248,7 +265,6 @@ Item {
|
|||
appSettings.neverAskAboutUnfurlingAgain = true
|
||||
}
|
||||
width: parent.width
|
||||
// height: 43
|
||||
anchors.top: sep2.bottom
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,95 @@
|
|||
import QtQuick 2.13
|
||||
import QtGraphicalEffects 1.13
|
||||
import QtQuick.Layouts 1.13
|
||||
import "./" as MessageComponents
|
||||
import "../../../../../imports"
|
||||
import "../../../../../shared"
|
||||
|
||||
Item {
|
||||
id: root
|
||||
default property alias inner: contents.children
|
||||
property bool isCurrentUser: false
|
||||
readonly property int smallCorner: Style.current.radius / 2
|
||||
readonly property int bigCorner: Style.current.radius * 2
|
||||
readonly property int fakeCornerSize: bigCorner * 2
|
||||
|
||||
Rectangle {
|
||||
width: parent.width + 2
|
||||
height: parent.height + 2
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.topMargin: -1
|
||||
anchors.leftMargin: -1
|
||||
radius: root.bigCorner
|
||||
border.width: 2
|
||||
border.color: Style.current.border
|
||||
}
|
||||
Rectangle {
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.bottomMargin: -1
|
||||
anchors.leftMargin: -1
|
||||
width: root.fakeCornerSize
|
||||
height: root.fakeCornerSize
|
||||
radius: root.smallCorner
|
||||
visible: !root.isCurrentUser
|
||||
border.width: 2
|
||||
border.color: Style.current.border
|
||||
}
|
||||
Rectangle {
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.right: parent.right
|
||||
anchors.bottomMargin: -1
|
||||
anchors.rightMargin: -1
|
||||
width: root.fakeCornerSize
|
||||
height: root.fakeCornerSize
|
||||
radius: root.smallCorner
|
||||
visible: root.isCurrentUser
|
||||
border.width: 2
|
||||
border.color: Style.current.border
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: Style.current.background
|
||||
|
||||
layer.enabled: true
|
||||
layer.effect: OpacityMask {
|
||||
maskSource: Item {
|
||||
width: root.width
|
||||
height: root.height
|
||||
|
||||
Rectangle {
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
width: parent.width
|
||||
height: parent.height
|
||||
radius: root.bigCorner
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.left: parent.left
|
||||
width: root.fakeCornerSize
|
||||
height: root.fakeCornerSize
|
||||
radius: root.smallCorner
|
||||
visible: !root.isCurrentUser
|
||||
}
|
||||
Rectangle {
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.right: parent.right
|
||||
width: root.fakeCornerSize
|
||||
height: root.fakeCornerSize
|
||||
radius: root.smallCorner
|
||||
visible: root.isCurrentUser
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
id: contents
|
||||
width: root.width
|
||||
height: root.height
|
||||
}
|
||||
}
|
||||
}
|
|
@ -4,8 +4,6 @@ import "../../../../../imports"
|
|||
|
||||
Item {
|
||||
property var clickMessage: function () {}
|
||||
property string imageUrls: ""
|
||||
property bool showImages: appSettings.displayChatImages && root.imageUrls !== ""
|
||||
property string linkUrls: ""
|
||||
property bool isCurrentUser: false
|
||||
property int contentType: 2
|
||||
|
@ -127,6 +125,7 @@ Item {
|
|||
anchors.right: parent.right
|
||||
anchors.rightMargin: chatBox.chatHorizontalPadding
|
||||
container: root.container
|
||||
chatHorizontalPadding: chatBox.chatHorizontalPadding
|
||||
}
|
||||
|
||||
ChatText {
|
||||
|
@ -210,10 +209,10 @@ Item {
|
|||
|
||||
ChatTime {
|
||||
id: chatTime
|
||||
anchors.top: root.showImages ? imageLoader.bottom : chatBox.bottom
|
||||
anchors.top: linksLoader.active ? linksLoader.bottom : chatBox.bottom
|
||||
anchors.topMargin: 4
|
||||
anchors.bottomMargin: Style.current.padding
|
||||
anchors.right: root.showImages ? imageLoader.right : chatBox.right
|
||||
anchors.right: linksLoader.active ? linksLoader.right : chatBox.right
|
||||
anchors.rightMargin: root.isCurrentUser ? 5 : Style.current.padding
|
||||
}
|
||||
|
||||
|
@ -234,30 +233,6 @@ Item {
|
|||
anchors.bottomMargin: Style.current.padding
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: imageLoader
|
||||
active: root.showImages
|
||||
sourceComponent: imageComponent
|
||||
anchors.left: !root.isCurrentUser ? chatImage.right : undefined
|
||||
anchors.leftMargin: !root.isCurrentUser ? 8 : 0
|
||||
anchors.right: !root.isCurrentUser ? undefined : parent.right
|
||||
anchors.rightMargin: !root.isCurrentUser ? 0 : Style.current.padding
|
||||
anchors.top: chatBox.bottom
|
||||
anchors.topMargin: Style.current.smallPadding
|
||||
}
|
||||
|
||||
Component {
|
||||
id: imageComponent
|
||||
ImageMessage {
|
||||
isCurrentUser: root.isCurrentUser
|
||||
container: root.container
|
||||
imageUrls: root.imageUrls
|
||||
onClicked: {
|
||||
root.clickMessage(false, false, true, image)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: linksLoader
|
||||
active: !!root.linkUrls
|
||||
|
@ -266,11 +241,14 @@ Item {
|
|||
anchors.right: !root.isCurrentUser ? undefined : parent.right
|
||||
anchors.rightMargin: !root.isCurrentUser ? 0 : Style.current.padding
|
||||
anchors.top: chatBox.bottom
|
||||
anchors.topMargin: Style.current.smallPadding
|
||||
anchors.topMargin: Style.current.halfPadding
|
||||
anchors.bottomMargin: Style.current.halfPadding
|
||||
|
||||
sourceComponent: Component {
|
||||
LinksMessage {
|
||||
linkUrls: root.linkUrls
|
||||
container: root.container
|
||||
isCurrentUser: root.isCurrentUser
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,27 +16,6 @@ Item {
|
|||
id: previewableSites
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
const sites = profileModel.getLinkPreviewWhitelist()
|
||||
try {
|
||||
const sitesJSON = JSON.parse(sites)
|
||||
let settingUpdadted = false
|
||||
sitesJSON.forEach(function (site) {
|
||||
if (appSettings.whitelistedUnfurlingSites[site.address] === undefined) {
|
||||
appSettings.whitelistedUnfurlingSites[site.address] = false
|
||||
settingUpdadted = true
|
||||
}
|
||||
|
||||
previewableSites.append(site)
|
||||
})
|
||||
if (settingUpdadted) {
|
||||
applicationWindow.whitelistChanged()
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Could not parse the whitelist for sites', e)
|
||||
}
|
||||
}
|
||||
|
||||
property Component dappListPopup: DappList {
|
||||
onClosed: destroy()
|
||||
}
|
||||
|
@ -153,23 +132,34 @@ Item {
|
|||
//% "Privacy"
|
||||
text: qsTrId("privacy")
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
id: displayImageSettings
|
||||
StyledText {
|
||||
//% "Display images in chat automatically"
|
||||
text: qsTrId("display-images-in-chat-automatically")
|
||||
spacing: Style.current.padding
|
||||
width: parent.width
|
||||
Column {
|
||||
Layout.fillWidth: true
|
||||
StyledText {
|
||||
//% "Display images in chat automatically"
|
||||
text: qsTrId("display-images-in-chat-automatically")
|
||||
font.pixelSize: 15
|
||||
font.weight: Font.Medium
|
||||
}
|
||||
StyledText {
|
||||
width: parent.width
|
||||
text: qsTr("All images (links that contain an image extension) will be downloaded and displayed, regardless of the whitelist settings below")
|
||||
font.pixelSize: 15
|
||||
font.weight: Font.Thin
|
||||
color: Style.current.secondaryText
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
}
|
||||
StatusSwitch {
|
||||
id: displayChatImagesSwitch
|
||||
Layout.rightMargin: 0
|
||||
checked: appSettings.displayChatImages
|
||||
onCheckedChanged: function (value) {
|
||||
appSettings.displayChatImages = this.checked
|
||||
}
|
||||
}
|
||||
StyledText {
|
||||
//% "under development"
|
||||
text: qsTrId("under-development")
|
||||
}
|
||||
}
|
||||
|
||||
StatusSectionHeadline {
|
||||
|
@ -185,6 +175,17 @@ Item {
|
|||
text: qsTr("Websites")
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: applicationWindow
|
||||
onSettingsLoaded: {
|
||||
let whitelist = JSON.parse(profileModel.getLinkPreviewWhitelist())
|
||||
whitelist.forEach(entry => {
|
||||
entry.isWhitelisted = appSettings.whitelistedUnfurlingSites[entry.address] || false
|
||||
previewableSites.append(entry)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
ListView {
|
||||
id: sitesListView
|
||||
width: parent.width
|
||||
|
@ -214,9 +215,11 @@ Item {
|
|||
}
|
||||
|
||||
StatusSwitch {
|
||||
checked: !!appSettings.whitelistedUnfurlingSites[address]
|
||||
checked: !!isWhitelisted
|
||||
onCheckedChanged: function () {
|
||||
changeUnfurlingWhitelist(address, this.checked)
|
||||
const settings = appSettings.whitelistedUnfurlingSites
|
||||
settings[address] = this.checked
|
||||
appSettings.whitelistedUnfurlingSites = settings
|
||||
}
|
||||
anchors.verticalCenter: siteTitle.bottom
|
||||
anchors.right: parent.right
|
||||
|
|
|
@ -222,4 +222,15 @@ QtObject {
|
|||
return [false, ""];
|
||||
}
|
||||
}
|
||||
|
||||
function getHostname(url) {
|
||||
const rgx = /\:\/\/(?:[a-zA-Z0-9\-]*\.{1,}){1,}[a-zA-Z0-9]*/i
|
||||
const matches = rgx.exec(url)
|
||||
if (!matches || !matches.length) return ""
|
||||
return matches[0].substring(3)
|
||||
}
|
||||
|
||||
function hasImageExtension(url) {
|
||||
return [".png", ".jpg", ".jpeg", ".svg", ".gif"].some(ext => url.includes(ext))
|
||||
}
|
||||
}
|
||||
|
|
38
ui/main.qml
38
ui/main.qml
|
@ -162,20 +162,42 @@ ApplicationWindow {
|
|||
property bool compatibilityMode: defaultAppSettings.compatibilityMode
|
||||
}
|
||||
|
||||
signal whitelistChanged()
|
||||
|
||||
function changeUnfurlingWhitelist(site, enabled) {
|
||||
appSettings.whitelistedUnfurlingSites[site] = enabled
|
||||
applicationWindow.whitelistChanged()
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: profileModel
|
||||
onProfileSettingsFileChanged: {
|
||||
settingsLoaded()
|
||||
if (appSettings.locale !== "en") {
|
||||
profileModel.changeLocale(appSettings.locale)
|
||||
}
|
||||
const whitelist = profileModel.getLinkPreviewWhitelist()
|
||||
try {
|
||||
const whiteListedSites = JSON.parse(whitelist)
|
||||
let settingsUpdated = false
|
||||
const settings = appSettings.whitelistedUnfurlingSites
|
||||
const whitelistedHostnames = []
|
||||
|
||||
// Add whitelisted sites in to app settings that are not already there
|
||||
whiteListedSites.forEach(site => {
|
||||
if (!settings.hasOwnProperty(site.address)) {
|
||||
settings[site.address] = false
|
||||
settingsUpdated = true
|
||||
}
|
||||
whitelistedHostnames.push(site.address)
|
||||
})
|
||||
// Remove any whitelisted sites from app settings that don't exist in the
|
||||
// whitelist from status-go
|
||||
Object.keys(settings).forEach(settingsHostname => {
|
||||
if (!whitelistedHostnames.includes(settingsHostname)) {
|
||||
delete settings[settingsHostname]
|
||||
settingsUpdated = true
|
||||
}
|
||||
})
|
||||
if (settingsUpdated) {
|
||||
appSettings.whitelistedUnfurlingSites = settings
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Could not parse the whitelist for sites', e)
|
||||
}
|
||||
applicationWindow.settingsLoaded()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -150,7 +150,6 @@ DISTFILES += \
|
|||
app/AppLayouts/Chat/ChatColumn/MessageComponents/DateGroup.qml \
|
||||
app/AppLayouts/Chat/ChatColumn/MessageComponents/EmojiReactions.qml \
|
||||
app/AppLayouts/Chat/ChatColumn/MessageComponents/ImageLoader.qml \
|
||||
app/AppLayouts/Chat/ChatColumn/MessageComponents/ImageMessage.qml \
|
||||
app/AppLayouts/Chat/ChatColumn/MessageComponents/LinksMessage.qml \
|
||||
app/AppLayouts/Chat/ChatColumn/MessageComponents/MessageMouseArea.qml \
|
||||
app/AppLayouts/Chat/ChatColumn/MessageComponents/NormalMessage.qml \
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit cdca42b90f7fd5f302a5eb31802b9ae526d566d7
|
||||
Subproject commit 149877a939656f3780fb73f5add26e5e205cf26f
|
Loading…
Reference in New Issue