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:
emizzle 2020-12-11 11:53:44 +11:00 committed by Iuri Matias
parent 810ce12a56
commit 6a0a75888b
18 changed files with 342 additions and 296 deletions

View File

@ -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",

View File

@ -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

View File

@ -57,7 +57,6 @@ type Message* = object
isCurrentUser*: bool
stickerHash*: string
outgoingStatus*: string
imageUrls*: string
linkUrls*: string
image*: string
audio*: string

View File

@ -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)

View File

@ -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

View File

@ -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
}

View File

@ -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

View File

@ -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
}
}
}

View File

@ -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

View File

@ -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 {}
}

View File

@ -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
}
}

View File

@ -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
}
}
}

View File

@ -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
}
}
}

View File

@ -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

View File

@ -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))
}
}

View File

@ -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()
}
}

View File

@ -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 \

2
vendor/status-go vendored

@ -1 +1 @@
Subproject commit cdca42b90f7fd5f302a5eb31802b9ae526d566d7
Subproject commit 149877a939656f3780fb73f5add26e5e205cf26f