feature: support unfurled Status links (contact/community/channel) (#12303)

* chore: move `LinkPreviewThumbnail` to a separate file
This commit is contained in:
Igor Sirotin 2023-10-13 14:36:07 +01:00 committed by GitHub
parent 9581e6deb6
commit 520d34240a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 1177 additions and 266 deletions

View File

@ -11,6 +11,9 @@ include ../../app_service/service/accounts/utils
QtObject:
type Utils* = ref object of QObject
proc isCompressedPubKey*(self: Utils, publicKey: string): bool
proc getDecompressedPk*(self: Utils, compressedKey: string): string
proc setup(self: Utils) =
self.QObject.setup
@ -129,13 +132,22 @@ QtObject:
result = escape_html(text)
proc getEmojiHashAsJson*(self: Utils, publicKey: string): string {.slot.} =
procs_from_visual_identity_service.getEmojiHashAsJson(publicKey)
var pk = publicKey
if self.isCompressedPubKey(publicKey):
pk = self.getDecompressedPk(publicKey)
procs_from_visual_identity_service.getEmojiHashAsJson(pk)
proc getColorHashAsJson*(self: Utils, publicKey: string): string {.slot.} =
procs_from_visual_identity_service.getColorHashAsJson(publicKey)
var pk = publicKey
if self.isCompressedPubKey(publicKey):
pk = self.getDecompressedPk(publicKey)
procs_from_visual_identity_service.getColorHashAsJson(pk)
proc getColorId*(self: Utils, publicKey: string): int {.slot.} =
int(procs_from_visual_identity_service.colorIdOf(publicKey))
var pk = publicKey
if self.isCompressedPubKey(publicKey):
pk = self.getDecompressedPk(publicKey)
int(procs_from_visual_identity_service.colorIdOf(pk))
proc getCompressedPk*(self: Utils, publicKey: string): string {.slot.} =
compressPk(publicKey)

View File

@ -192,8 +192,8 @@ proc getLinkPreviewEnabled*(self: Controller): bool =
proc canAskToEnableLinkPreview(self: Controller): bool =
return self.linkPreviewPersistentSetting == LinkPreviewSetting.AlwaysAsk and self.linkPreviewCurrentMessageSetting == LinkPreviewSetting.AlwaysAsk
proc setText*(self: Controller, text: string) =
if(text == ""):
proc setText*(self: Controller, text: string, unfurlNewUrls: bool) =
if text == "":
self.resetLinkPreviews()
return
@ -204,7 +204,10 @@ proc setText*(self: Controller, text: string) =
let askToEnableLinkPreview = len(newUrls) > 0 and self.canAskToEnableLinkPreview()
self.delegate.setAskToEnableLinkPreview(askToEnableLinkPreview)
if self.getLinkPreviewEnabled() and len(urls) > 0:
if not unfurlNewUrls:
return
if self.getLinkPreviewEnabled() and len(newUrls) > 0:
self.messageService.asyncUnfurlUrls(newUrls)
proc linkPreviewsFromCache*(self: Controller, urls: seq[string]): Table[string, LinkPreview] =

View File

@ -96,7 +96,7 @@ method isFavorite*(self: AccessInterface, item: GifDto): bool {.base.} =
method viewDidLoad*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available")
method setText*(self: AccessInterface, text: string) {.base.} =
method setText*(self: AccessInterface, text: string, unfurlUrls: bool) {.base.} =
raise newException(ValueError, "No implementation available")
method setUrls*(self: AccessInterface, urls: seq[string]) {.base.} =

View File

@ -152,8 +152,8 @@ method addToRecentsGif*(self: Module, item: GifDto) =
method isFavorite*(self: Module, item: GifDto): bool =
return self.controller.isFavorite(item)
method setText*(self: Module, text: string) =
self.controller.setText(text)
method setText*(self: Module, text: string, unfurlUrls: bool) =
self.controller.setText(text, unfurlUrls)
method clearLinkPreviewCache*(self: Module) {.slot.} =
self.controller.clearLinkPreviewCache()

View File

@ -51,9 +51,11 @@ QtObject:
msg: string,
replyTo: string,
contentType: int) {.slot.} =
self.delegate.setText(msg, false)
self.delegate.sendChatMessage(msg, replyTo, contentType, self.linkPreviewModel.getUnfuledLinkPreviews())
proc sendImages*(self: View, imagePathsAndDataJson: string, msg: string, replyTo: string): string {.slot.} =
self.delegate.setText(msg, false)
self.delegate.sendImages(imagePathsAndDataJson, msg, replyTo, self.linkPreviewModel.getUnfuledLinkPreviews())
proc acceptAddressRequest*(self: View, messageId: string , address: string) {.slot.} =
@ -212,7 +214,7 @@ QtObject:
# Currently used to fetch link previews, but could be used elsewhere
proc setText*(self: View, text: string) {.slot.} =
self.delegate.setText(text)
self.delegate.setText(text, true)
proc updateLinkPreviewsFromCache*(self: View, urls: seq[string]) =
let linkPreviews = self.delegate.linkPreviewsFromCache(urls)

View File

@ -1,5 +1,7 @@
import strformat
import ../../../app_service/service/message/dto/link_preview
import ../../../app_service/service/message/dto/status_community_link_preview
import ../../../app_service/service/message/dto/status_community_channel_link_preview
type
Item* = ref object

View File

@ -1,20 +1,37 @@
import NimQml, strformat, tables, sequtils
import ./link_preview_item
import ../../../app_service/service/message/dto/link_preview
import ../../../app_service/service/message/dto/standard_link_preview
import ../../../app_service/service/message/dto/status_link_preview
import ../../../app_service/service/message/dto/status_contact_link_preview
import ../../../app_service/service/message/dto/status_community_link_preview
import ../../../app_service/service/message/dto/status_community_channel_link_preview
type
ModelRole {.pure.} = enum
Url = UserRole + 1
Unfurled
Immutable
Hostname
Title
Description
LinkType
ThumbnailWidth
ThumbnailHeight
ThumbnailUrl
ThumbnailDataUri
Empty
PreviewType
# Standard unfurled link (oembed, opengraph, image)
StandardPreview
StandardPreviewThumbnail
# Status contact
StatusContactPreview
StatusContactPreviewThumbnail
# Status community
StatusCommunityPreview
StatusCommunityPreviewIcon
StatusCommunityPreviewBanner
# Status channel
StatusCommunityChannelPreview
# NOTE: I know "CommunityChannelCommunity" doesn't sound good,
# and we could use existing `StatusCommunityPreview` role for this,
# but I decided no to mess things around. So there we have it:
StatusCommunityChannelCommunityPreview
StatusCommunityChannelCommunityPreviewIcon
StatusCommunityChannelCommunityPreviewBanner
QtObject:
type
@ -66,14 +83,23 @@ QtObject:
ModelRole.Url.int:"url",
ModelRole.Unfurled.int:"unfurled",
ModelRole.Immutable.int:"immutable",
ModelRole.Hostname.int:"hostname",
ModelRole.Title.int:"title",
ModelRole.Description.int:"description",
ModelRole.LinkType.int:"linkType",
ModelRole.ThumbnailWidth.int:"thumbnailWidth",
ModelRole.ThumbnailHeight.int:"thumbnailHeight",
ModelRole.ThumbnailUrl.int:"thumbnailUrl",
ModelRole.ThumbnailDataUri.int:"thumbnailDataUri",
ModelRole.Empty.int:"empty",
ModelRole.PreviewType.int:"previewType",
# Standard
ModelRole.StandardPreview.int:"standardPreview",
ModelRole.StandardPreviewThumbnail.int:"standardPreviewThumbnail",
# Contact
ModelRole.StatusContactPreview.int:"statusContactPreview",
ModelRole.StatusContactPreviewThumbnail.int:"statusContactPreviewThumbnail",
# Community
ModelRole.StatusCommunityPreview.int:"statusCommunityPreview",
ModelRole.StatusCommunityPreviewIcon.int:"statusCommunityPreviewIcon",
ModelRole.StatusCommunityPreviewBanner.int:"statusCommunityPreviewBanner",
# Channel
ModelRole.StatusCommunityChannelPreview.int:"statusCommunityChannelPreview",
ModelRole.StatusCommunityChannelCommunityPreview.int:"statusCommunityChannelCommunityPreview",
ModelRole.StatusCommunityChannelCommunityPreviewIcon.int:"statusCommunityChannelCommunityPreviewIcon",
ModelRole.StatusCommunityChannelCommunityPreviewBanner.int:"statusCommunityChannelCommunityPreviewBanner",
}.toTable
method data(self: Model, index: QModelIndex, role: int): QVariant =
@ -93,22 +119,45 @@ QtObject:
result = newQVariant(item.unfurled)
of ModelRole.Immutable:
result = newQVariant(item.immutable)
of ModelRole.Hostname:
result = newQVariant(item.linkPreview.hostname)
of ModelRole.Title:
result = newQVariant(item.linkPreview.title)
of ModelRole.Description:
result = newQVariant(item.linkPreview.description)
of ModelRole.LinkType:
result = newQVariant(item.linkPreview.linkType.int)
of ModelRole.ThumbnailWidth:
result = newQVariant(item.linkPreview.thumbnail.width)
of ModelRole.ThumbnailHeight:
result = newQVariant(item.linkPreview.thumbnail.height)
of ModelRole.ThumbnailUrl:
result = newQVariant(item.linkPreview.thumbnail.url)
of ModelRole.ThumbnailDataUri:
result = newQVariant(item.linkPreview.thumbnail.dataUri)
of ModelRole.Empty:
result = newQVariant(item.linkPreview.empty())
of ModelRole.PreviewType:
result = newQVariant(item.linkPreview.previewType.int)
of ModelRole.StandardPreview:
if item.linkPreview.standardPreview != nil:
result = newQVariant(item.linkPreview.standardPreview)
of ModelRole.StandardPreviewThumbnail:
if item.linkPreview.standardPreview != nil:
result = newQVariant(item.linkPreview.standardPreview.getThumbnail())
of ModelRole.StatusContactPreview:
if item.linkPreview.statusContactPreview != nil:
result = newQVariant(item.linkPreview.statusContactPreview)
of ModelRole.StatusContactPreviewThumbnail:
if item.linkPreview.statusContactPreview != nil:
result = newQVariant(item.linkPreview.statusContactPreview.getIcon())
of ModelRole.StatusCommunityPreview:
if item.linkPreview.statusCommunityPreview != nil:
result = newQVariant(item.linkPreview.statusCommunityPreview)
of ModelRole.StatusCommunityPreviewIcon:
if item.linkPreview.statusCommunityPreview != nil:
result = newQVariant(item.linkPreview.statusCommunityPreview.getIcon())
of ModelRole.StatusCommunityPreviewBanner:
if item.linkPreview.statusCommunityPreview != nil:
result = newQVariant(item.linkPreview.statusCommunityPreview.getBanner())
of ModelRole.StatusCommunityChannelPreview:
if item.linkPreview.statusCommunityChannelPreview != nil:
result = newQVariant(item.linkPreview.statusCommunityChannelPreview)
of ModelRole.StatusCommunityChannelCommunityPreview:
if (let community = item.linkPreview.getChannelCommunity(); community) != nil:
result = newQVariant(community)
of ModelRole.StatusCommunityChannelCommunityPreviewIcon:
if (let community = item.linkPreview.getChannelCommunity(); community) != nil:
result = newQVariant(community.getIcon())
of ModelRole.StatusCommunityChannelCommunityPreviewBanner:
if (let community = item.linkPreview.getChannelCommunity(); community) != nil:
result = newQVariant(community.getBanner())
else:
result = newQVariant()
proc removeItemWithIndex(self: Model, ind: int) =
if(ind < 0 or ind >= self.items.len):
@ -220,7 +269,7 @@ QtObject:
proc getUnfuledLinkPreviews*(self: Model): seq[LinkPreview] =
result = @[]
for item in self.items:
if item.unfurled and item.linkPreview.hostName != "":
if item.unfurled and not item.linkPreview.empty():
result.add(item.linkPreview)
proc getLinks*(self: Model): seq[string] =

View File

@ -1,85 +1,128 @@
import json, strformat, tables
import ./link_preview_thumbnail, ./status_link_preview, ./standard_link_preview
import ./status_contact_link_preview, ./status_community_link_preview, ./status_community_channel_link_preview
include ../../../common/json_utils
type
LinkType* {.pure.} = enum
Link = 0
Image
proc toLinkType*(value: int): LinkType =
try:
return LinkType(value)
except RangeDefect:
return LinkType.Link
type
LinkPreviewThumbnail* = object
width*: int
height*: int
url*: string
dataUri*: string
PreviewType {.pure.} = enum
NoPreview = 0
StandardPreview
StatusContactPreview
StatusCommunityPreview
StatusCommunityChannelPreview
type
LinkPreview* = ref object
url*: string
hostname*: string
title*: string
description*: string
thumbnail*: LinkPreviewThumbnail
linkType*: LinkType
previewType*: PreviewType
standardPreview*: StandardLinkPreview
statusContactPreview*: StatusContactLinkPreview
statusCommunityPreview*: StatusCommunityLinkPreview
statusCommunityChannelPreview*: StatusCommunityChannelLinkPreview
proc delete*(self: LinkPreview) =
discard
if self.standardPreview != nil:
self.standardPreview.delete
if self.statusContactPreview != nil:
self.statusContactPreview.delete
if self.statusCommunityPreview != nil:
self.statusCommunityPreview.delete
if self.statusCommunityChannelPreview != nil:
self.statusCommunityChannelPreview.delete
proc initLinkPreview*(url: string): LinkPreview =
result = LinkPreview()
result.url = url
result.previewType = PreviewType.NoPreview
proc toLinkPreviewThumbnail*(jsonObj: JsonNode): LinkPreviewThumbnail =
result = LinkPreviewThumbnail()
discard jsonObj.getProp("width", result.width)
discard jsonObj.getProp("height", result.height)
discard jsonObj.getProp("url", result.url)
discard jsonObj.getProp("dataUri", result.dataUri)
proc toLinkPreview*(jsonObj: JsonNode): LinkPreview =
proc toLinkPreview*(jsonObj: JsonNode, standard: bool): LinkPreview =
result = LinkPreview()
result.previewType = PreviewType.NoPreview
if standard:
discard jsonObj.getProp("url", result.url)
discard jsonObj.getProp("hostname", result.hostname)
discard jsonObj.getProp("title", result.title)
discard jsonObj.getProp("description", result.description)
discard jsonObj.getProp("hostname", result.hostname)
result.linkType = toLinkType(jsonObj["type"].getInt)
var thumbnail: JsonNode
if jsonObj.getProp("thumbnail", thumbnail):
result.thumbnail = toLinkPreviewThumbnail(thumbnail)
proc `$`*(self: LinkPreviewThumbnail): string =
result = fmt"""LinkPreviewThumbnail(
width: {self.width},
height: {self.height},
urlLength: {self.url.len},
dataUriLength: {self.dataUri.len}
)"""
result.previewType = PreviewType.StandardPreview
result.standardPreview = toStandardLinkPreview(jsonObj)
else:
discard jsonObj.getProp("url", result.url)
var node: JsonNode
if jsonObj.getProp("contact", node):
result.previewType = PreviewType.StatusContactPreview
result.statusContactPreview = toStatusContactLinkPreview(node)
elif jsonObj.getProp("community", node):
result.previewType = PreviewType.StatusCommunityPreview
result.statusCommunityPreview = toStatusCommunityLinkPreview(node)
elif jsonObj.getProp("channel", node):
result.previewType = PreviewType.StatusCommunityChannelPreview
result.statusCommunityChannelPreview = toStatusCommunityChannelLinkPreview(node)
proc `$`*(self: LinkPreview): string =
let standardPreview = if self.standardPreview != nil: $self.standardPreview else: ""
let contactPreview = if self.statusContactPreview != nil: $self.statusContactPreview else: ""
let communityPreview = if self.statusCommunityPreview != nil: $self.statusCommunityPreview else: ""
let channelPreview = if self.statusCommunityChannelPreview != nil: $self.statusCommunityChannelPreview else: ""
result = fmt"""LinkPreview(
type: {self.linkType},
url: {self.url},
hostname: {self.hostname},
title: {self.title},
description: {self.description},
thumbnail: {self.thumbnail}
previewType: {self.previewType},
standardPreview: {standardPreview},
contactPreview: {contactPreview},
communityPreview: {communityPreview},
channelPreview: {channelPreview}
)"""
# Custom JSON converter to force `linkType` integer instead of string
proc `%`*(self: LinkPreview): JsonNode =
result = %* {
"type": self.linkType.int,
"url": self.url,
"hostname": self.hostname,
"title": self.title,
"description": self.description,
"thumbnail": %self.thumbnail,
"standardPreview": %self.standardPreview,
"contactPreview": %self.statusContactPreview,
"communityPreview": %self.statusCommunityPreview,
"channelPreview": %self.statusCommunityChannelPreview
}
proc empty*(self: LinkPreview): bool =
case self.previewType:
of PreviewType.StandardPreview:
return self.standardPreview == nil or self.standardPreview.empty()
of PreviewType.StatusContactPreview:
return self.statusContactPreview == nil or self.statusContactPreview.empty()
of PreviewType.StatusCommunityPreview:
return self.statusCommunityPreview == nil or self.statusCommunityPreview.empty()
of PreviewType.StatusCommunityChannelPreview:
return self.statusCommunityChannelPreview == nil or self.statusCommunityChannelPreview.empty()
else:
return true
proc extractLinkPreviewsLists*(input: seq[LinkPreview]): (seq[StandardLinkPreview], seq[StatusLinkPreview]) =
var standard: seq[StandardLinkPreview]
var status: seq[StatusLinkPreview]
for preview in input:
case preview.previewType:
of PreviewType.StandardPreview:
if preview.standardPreview != nil:
preview.standardPreview.url = preview.url
standard.add(preview.standardPreview)
of PreviewType.StatusContactPreview:
let statusLinkPreview = StatusLinkPreview()
statusLinkPreview.url = preview.url
statusLinkPreview.contact = preview.statusContactPreview
status.add(statusLinkPreview)
of PreviewType.StatusCommunityPreview:
let statusLinkPreview = StatusLinkPreview()
statusLinkPreview.url = preview.url
statusLinkPreview.community = preview.statusCommunityPreview
status.add(statusLinkPreview)
of PreviewType.StatusCommunityChannelPreview:
let statusLinkPreview = StatusLinkPreview()
statusLinkPreview.url = preview.url
statusLinkPreview.channel = preview.statusCommunityChannelPreview
status.add(statusLinkPreview)
else:
discard
return (standard, status)
proc getChannelCommunity*(self: LinkPreview): StatusCommunityLinkPreview =
if self.statusCommunityChannelPreview == nil:
return nil
return self.statusCommunityChannelPreview.getCommunity()

View File

@ -0,0 +1,84 @@
import json, strformat, NimQml, chronicles
include ../../../common/json_utils
QtObject:
type LinkPreviewThumbnail* = ref object of QObject
width: int
height: int
url: string
dataUri: string
proc setup*(self: LinkPreviewThumbnail) =
self.QObject.setup()
proc delete*(self: LinkPreviewThumbnail) =
self.QObject.delete()
proc update*(self: LinkPreviewThumbnail, width: int, height: int, url: string, dataUri: string) =
self.width = width
self.height = height
self.url = url
self.dataUri = dataUri
proc copy*(self: LinkPreviewThumbnail, other: LinkPreviewThumbnail) =
if other != nil:
self.update(other.width, other.height, other.url, other.dataUri)
else:
self.update(0, 0, "", "")
proc newLinkPreviewThumbnail*(width: int = 0, height: int = 0, url: string = "", dataUri: string = ""): LinkPreviewThumbnail =
new(result, delete)
result.setup()
result.update(width, height, url, dataUri)
proc widthChanged*(self: LinkPreviewThumbnail) {.signal.}
proc getWidth*(self: LinkPreviewThumbnail): int {.slot.} =
result = self.width
QtProperty[int] width:
read = getWidth
notify = widthChanged
proc heightChanged*(self: LinkPreviewThumbnail) {.signal.}
proc getHeight*(self: LinkPreviewThumbnail): int {.slot.} =
result = self.height
QtProperty[int] height:
read = getHeight
notify = heightChanged
proc urlChanged*(self: LinkPreviewThumbnail) {.signal.}
proc getUrl*(self: LinkPreviewThumbnail): string {.slot.} =
result = self.url
QtProperty[string] url:
read = getUrl
notify = urlChanged
proc dataUriChanged*(self: LinkPreviewThumbnail) {.signal.}
proc getDataUri*(self: LinkPreviewThumbnail): string {.slot.} =
result = self.dataUri
QtProperty[string] dataUri:
read = getDataUri
notify = dataUriChanged
proc toLinkPreviewThumbnail*(jsonObj: JsonNode): LinkPreviewThumbnail =
result = LinkPreviewThumbnail()
discard jsonObj.getProp("width", result.width)
discard jsonObj.getProp("height", result.height)
discard jsonObj.getProp("url", result.url)
discard jsonObj.getProp("dataUri", result.dataUri)
proc `$`*(self: LinkPreviewThumbnail): string =
result = fmt"""LinkPreviewThumbnail(
width: {self.width},
height: {self.height},
urlLength: {self.url.len},
dataUriLength: {self.dataUri.len}
)"""
proc `%`*(self: LinkPreviewThumbnail): JsonNode =
result = %*{
"width": self.width,
"height": self.height,
"url": self.url,
"dataUri": self.dataUri
}

View File

@ -1,6 +1,6 @@
{.used.}
import json, strutils
import json, strutils, chronicles
import ../../../common/types
import link_preview
@ -263,7 +263,12 @@ proc toMessageDto*(jsonObj: JsonNode): MessageDto =
var linkPreviewsArr: JsonNode
if jsonObj.getProp("linkPreviews", linkPreviewsArr):
for element in linkPreviewsArr.getElems():
result.linkPreviews.add(element.toLinkPreview())
result.linkPreviews.add(element.toLinkPreview(true))
var statusLinkPreviewsArr: JsonNode
if jsonObj.getProp("statusLinkPreviews", statusLinkPreviewsArr):
for element in statusLinkPreviewsArr.getElems():
result.linkPreviews.add(element.toLinkPreview(false))
var parsedTextArr: JsonNode
if(jsonObj.getProp("parsedText", parsedTextArr) and parsedTextArr.kind == JArray):

View File

@ -0,0 +1,114 @@
import json, strformat, NimQml
import ./link_preview_thumbnail
include ../../../common/json_utils
type
LinkType* {.pure.} = enum
Link = 0
Image
proc toLinkType*(value: int): LinkType =
try:
return LinkType(value)
except RangeDefect:
return LinkType.Link
QtObject:
type StandardLinkPreview* = ref object of QObject
url*: string # this property is set manually and only used in`toJSON` conversion
hostname: string
title: string
description: string
linkType: LinkType
thumbnail: LinkPreviewThumbnail
proc setup*(self: StandardLinkPreview) =
self.QObject.setup
self.thumbnail = newLinkPreviewThumbnail()
proc delete*(self: StandardLinkPreview) =
self.QObject.delete
self.thumbnail.delete
proc newStandardLinkPreview*(hostname: string, title: string, description: string, thumbnail: LinkPreviewThumbnail, linkType: LinkType): StandardLinkPreview =
new(result, delete)
result.setup()
result.hostname = hostname
result.title = title
result.description = description
result.linkType = linkType
result.thumbnail.copy(thumbnail)
proc hostnameChanged*(self: StandardLinkPreview) {.signal.}
proc getHostname*(self: StandardLinkPreview): string {.slot.} =
result = self.hostname
QtProperty[string] hostname:
read = getHostname
notify = hostnameChanged
proc titleChanged*(self: StandardLinkPreview) {.signal.}
proc getTitle*(self: StandardLinkPreview): string {.slot.} =
result = self.title
QtProperty[string] title:
read = getTitle
notify = titleChanged
proc descriptionChanged*(self: StandardLinkPreview) {.signal.}
proc getDescription*(self: StandardLinkPreview): string {.slot.} =
result = self.description
QtProperty[string] description:
read = getDescription
notify = descriptionChanged
proc linkTypeChanged*(self: StandardLinkPreview) {.signal.}
proc getLinkType*(self: StandardLinkPreview): int {.slot.} =
result = self.linkType.int
QtProperty[int] linkType:
read = getLinkType
notify = linkTypeChanged
proc getThumbnail*(self: StandardLinkPreview): LinkPreviewThumbnail =
result = self.thumbnail
proc toStandardLinkPreview*(jsonObj: JsonNode): StandardLinkPreview =
var hostname: string
var title: string
var description: string
var linkType: LinkType
var thumbnail: LinkPreviewThumbnail
discard jsonObj.getProp("hostname", hostname)
discard jsonObj.getProp("title", title)
discard jsonObj.getProp("description", description)
linkType = toLinkType(jsonObj["type"].getInt)
var thumbnailJson: JsonNode
if jsonObj.getProp("thumbnail", thumbnailJson):
thumbnail = toLinkPreviewThumbnail(thumbnailJson)
result = newStandardLinkPreview(hostname, title, description, thumbnail, linkType)
proc `$`*(self: StandardLinkPreview): string =
result = fmt"""StandardLinkPreview(
type: {self.linkType},
hostname: {self.hostname},
title: {self.title},
description: {self.description},
thumbnail: {self.thumbnail}
)"""
# Custom JSON converter to force `linkType` integer instead of string
proc `%`*(self: StandardLinkPreview): JsonNode =
result = %* {
"url": self.url,
"type": self.linkType.int,
"hostname": self.hostname,
"title": self.title,
"description": self.description,
"thumbnail": %self.thumbnail,
}
proc empty*(self: StandardLinkPreview): bool =
return self.hostname.len == 0

View File

@ -0,0 +1,95 @@
import json, strformat, NimQml, chronicles
import link_preview_thumbnail
import status_community_link_preview
include ../../../common/json_utils
QtObject:
type StatusCommunityChannelLinkPreview* = ref object of QObject
channelUuid: string
emoji: string
displayName: string
description: string
color: string
community: StatusCommunityLinkPreview
proc setup*(self: StatusCommunityChannelLinkPreview) =
self.QObject.setup()
proc delete*(self: StatusCommunityChannelLinkPreview) =
self.QObject.delete()
self.community.delete()
proc channelUuidChanged*(self: StatusCommunityChannelLinkPreview) {.signal.}
proc getChannelUuid*(self: StatusCommunityChannelLinkPreview): string {.slot.} =
return self.channelUuid
QtProperty[string] channelUuid:
read = getChannelUuid
notify = channelUuidChanged
proc emojiChanged*(self: StatusCommunityChannelLinkPreview) {.signal.}
proc getEmoji*(self: StatusCommunityChannelLinkPreview): string {.slot.} =
return self.emoji
QtProperty[string] emoji:
read = getEmoji
notify = emojiChanged
proc displayNameChanged*(self: StatusCommunityChannelLinkPreview) {.signal.}
proc getDisplayName*(self: StatusCommunityChannelLinkPreview): string {.slot.} =
return self.displayName
QtProperty[string] displayName:
read = getDisplayName
notify = displayNameChanged
proc descriptionChanged*(self: StatusCommunityChannelLinkPreview) {.signal.}
proc getDescription*(self: StatusCommunityChannelLinkPreview): string {.slot.} =
return self.description
QtProperty[string] description:
read = getDescription
notify = descriptionChanged
proc colorChanged*(self: StatusCommunityChannelLinkPreview) {.signal.}
proc getColor*(self: StatusCommunityChannelLinkPreview): string {.slot.} =
return self.color
QtProperty[string] color:
read = getColor
notify = colorChanged
proc getCommunity*(self: StatusCommunityChannelLinkPreview): StatusCommunityLinkPreview =
return self.community
proc toStatusCommunityChannelLinkPreview*(jsonObj: JsonNode): StatusCommunityChannelLinkPreview =
new(result, delete)
result.setup()
discard jsonObj.getProp("channelUuid", result.channelUuid)
discard jsonObj.getProp("emoji", result.emoji)
discard jsonObj.getProp("displayName", result.displayName)
discard jsonObj.getProp("description", result.description)
discard jsonObj.getProp("color", result.color)
var communityJsonNode: JsonNode
if jsonObj.getProp("community", communityJsonNode):
result.community = toStatusCommunityLinkPreview(communityJsonNode)
proc `$`*(self: StatusCommunityChannelLinkPreview): string =
return fmt"""StatusCommunityChannelLinkPreview(
channelUuid: {self.channelUuid},
emoji: {self.emoji},
displayName: {self.displayName},
description: {self.description},
color: {self.color},
community: {self.community}
)"""
proc `%`*(self: StatusCommunityChannelLinkPreview): JsonNode =
return %* {
"channelUuid": self.channelUuid,
"emoji": self.emoji,
"displayName": self.displayName,
"description": self.description,
"color": self.color,
"community": self.community
}
proc empty*(self: StatusCommunityChannelLinkPreview): bool =
return self.channelUUID.len == 0

View File

@ -0,0 +1,114 @@
import json, strformat, NimQml, chronicles
import link_preview_thumbnail
include ../../../common/json_utils
QtObject:
type StatusCommunityLinkPreview* = ref object of QObject
communityID: string
displayName: string
description: string
membersCount: int
color: string
icon: LinkPreviewThumbnail
banner: LinkPreviewThumbnail
proc setup*(self: StatusCommunityLinkPreview) =
self.QObject.setup()
self.icon = newLinkPreviewThumbnail()
self.banner = newLinkPreviewThumbnail()
proc delete*(self: StatusCommunityLinkPreview) =
self.QObject.delete()
self.icon.delete()
self.banner.delete()
proc communityIdChanged*(self: StatusCommunityLinkPreview) {.signal.}
proc getCommunityId*(self: StatusCommunityLinkPreview): string {.slot.} =
result = self.communityID
QtProperty[string] communityId:
read = getCommunityId
notify = communityIdChanged
proc displayNameChanged*(self: StatusCommunityLinkPreview) {.signal.}
proc getDisplayName*(self: StatusCommunityLinkPreview): string {.slot.} =
result = self.displayName
QtProperty[string] displayName:
read = getDisplayName
notify = displayNameChanged
proc descriptionChanged*(self: StatusCommunityLinkPreview) {.signal.}
proc getDescription*(self: StatusCommunityLinkPreview): string {.slot.} =
result = self.description
QtProperty[string] description:
read = getDescription
notify = descriptionChanged
proc membersCountChanged*(self: StatusCommunityLinkPreview) {.signal.}
proc getMembersCount*(self: StatusCommunityLinkPreview): int {.slot.} =
result = int(self.membersCount)
QtProperty[int] membersCount:
read = getMembersCount
notify = membersCountChanged
proc colorChanged*(self: StatusCommunityLinkPreview) {.signal.}
proc getColor*(self: StatusCommunityLinkPreview): string {.slot.} =
result = self.color
QtProperty[string] color:
read = getColor
notify = colorChanged
proc getIcon*(self: StatusCommunityLinkPreview): LinkPreviewThumbnail =
result = self.icon
proc getBanner*(self: StatusCommunityLinkPreview): LinkPreviewThumbnail =
result = self.banner
proc toStatusCommunityLinkPreview*(jsonObj: JsonNode): StatusCommunityLinkPreview =
new(result, delete)
result.setup()
var icon: LinkPreviewThumbnail
var banner: LinkPreviewThumbnail
discard jsonObj.getProp("communityId", result.communityID)
discard jsonObj.getProp("displayName", result.displayName)
discard jsonObj.getProp("description", result.description)
discard jsonObj.getProp("membersCount", result.membersCount)
discard jsonObj.getProp("color", result.color)
var iconJson: JsonNode
if jsonObj.getProp("icon", iconJson):
icon = toLinkPreviewThumbnail(iconJson)
var bannerJson: JsonNode
if jsonObj.getProp("banner", bannerJson):
banner = toLinkPreviewThumbnail(bannerJson)
result.icon.copy(icon)
result.banner.copy(banner)
proc `$`*(self: StatusCommunityLinkPreview): string =
result = fmt"""StatusCommunityLinkPreview(
communityId: {self.communityID},
displayName: {self.displayName},
description: {self.description},
membersCount: {self.membersCount},
color: {self.color},
icon: {self.icon},
banner: {self.banner}
)"""
proc `%`*(self: StatusCommunityLinkPreview): JsonNode =
result = %* {
"communityID": self.communityID,
"displayName": self.displayName,
"description": self.description,
"membersCount": self.membersCount,
"color": self.color,
"icon": self.icon,
"banner": self.banner
}
proc empty*(self: StatusCommunityLinkPreview): bool =
return self.communityID.len == 0

View File

@ -0,0 +1,87 @@
import json, strformat, NimQml, chronicles
import link_preview_thumbnail
include ../../../common/json_utils
QtObject:
type StatusContactLinkPreview* = ref object of QObject
publicKey: string
displayName: string
description: string
icon: LinkPreviewThumbnail
proc setup*(self: StatusContactLinkPreview) =
self.QObject.setup()
self.icon = newLinkPreviewThumbnail()
proc delete*(self: StatusContactLinkPreview) =
self.QObject.delete()
self.icon.delete()
proc newStatusContactLinkPreview*(publicKey: string, displayName: string, description: string, icon: LinkPreviewThumbnail): StatusContactLinkPreview =
new(result, delete)
result.setup()
result.publicKey = publicKey
result.displayName = displayName
result.description = description
result.icon.copy(icon)
proc publicKeyChanged*(self: StatusContactLinkPreview) {.signal.}
proc getPublicKey*(self: StatusContactLinkPreview): string {.slot.} =
result = self.publicKey
QtProperty[string] publicKey:
read = getPublicKey
notify = publicKeyChanged
proc displayNameChanged*(self: StatusContactLinkPreview) {.signal.}
proc getDisplayName*(self: StatusContactLinkPreview): string {.slot.} =
result = self.displayName
QtProperty[string] displayName:
read = getDisplayName
notify = displayNameChanged
proc descriptionChanged*(self: StatusContactLinkPreview) {.signal.}
proc getDescription*(self: StatusContactLinkPreview): string {.slot.} =
result = self.description
QtProperty[string] description:
read = getDescription
notify = descriptionChanged
proc getIcon*(self: StatusContactLinkPreview): LinkPreviewThumbnail =
result = self.icon
proc toStatusContactLinkPreview*(jsonObj: JsonNode): StatusContactLinkPreview =
var publicKey: string
var displayName: string
var description: string
var icon: LinkPreviewThumbnail
discard jsonObj.getProp("publicKey", publicKey)
discard jsonObj.getProp("displayName", displayName)
discard jsonObj.getProp("description", description)
var iconJson: JsonNode
if jsonObj.getProp("icon", iconJson):
icon = toLinkPreviewThumbnail(iconJson)
result = newStatusContactLinkPreview(publicKey, displayName, description, icon)
proc `$`*(self: StatusContactLinkPreview): string =
result = fmt"""StatusContactLinkPreview(
publicKey: {self.publicKey},
displayName: {self.displayName},
description: {self.description},
icon: {self.icon}
)"""
proc `%`*(self: StatusContactLinkPreview): JsonNode =
return %* {
"publicKey": self.publicKey,
"displayName": self.displayName,
"description": self.description,
"icon": self.icon
}
proc empty*(self: StatusContactLinkPreview): bool =
return self.publicKey.len == 0

View File

@ -0,0 +1,41 @@
import json, strformat, NimQml, chronicles
import link_preview_thumbnail
import status_contact_link_preview
import status_community_link_preview
import status_community_channel_link_preview
include ../../../common/json_utils
type StatusLinkPreview* = ref object
url*: string
contact*: StatusContactLinkPreview
community*: StatusCommunityLinkPreview
channel*: StatusCommunityChannelLinkPreview
proc toStatusLinkPreview*(jsonObj: JsonNode): StatusLinkPreview =
result = StatusLinkPreview()
discard jsonObj.getProp("url", result.url)
var contact: JsonNode
if jsonObj.getProp("contact", contact):
result.contact = toStatusContactLinkPreview(contact)
var community: JsonNode
if jsonObj.getProp("community", community):
result.community = toStatusCommunityLinkPreview(contact)
var channel: JsonNode
if jsonObj.getProp("channel", channel):
result.channel = toStatusCommunityChannelLinkPreview(contact)
proc `%`*(self: StatusLinkPreview): JsonNode =
var obj = %*{
"url": self.url
}
if self.contact != nil:
obj["contact"] = %*self.contact
if self.community != nil:
obj["community"] = %*self.community
if self.channel != nil:
obj["channel"] = %*self.channel
return obj

View File

@ -18,6 +18,7 @@ import ../chat/dto/chat as chat_dto
import ./dto/pinned_message_update as pinned_msg_update_dto
import ./dto/removed_message as removed_msg_dto
import ./dto/link_preview
import ./dto/status_link_preview
import ./message_cursor
import ../../common/message as message_common
@ -863,11 +864,20 @@ QtObject:
if responseObj.getProp("requestedUrls", requestedUrlsArr):
requestedUrls = map(requestedUrlsArr.getElems(), proc(x: JsonNode): string = x.getStr)
var linkPreviewsArr: JsonNode
let unfurlResponse = responseObj["response"]
var linkPreviews: Table[string, LinkPreview]
if responseObj.getProp("response", linkPreviewsArr):
var linkPreviewsArr: JsonNode
var statusLinkPreviewsArr: JsonNode
if unfurlResponse.getProp("linkPreviews", linkPreviewsArr):
for element in linkPreviewsArr.getElems():
let linkPreview = element.toLinkPreview()
let linkPreview = element.toLinkPreview(true)
linkPreviews[linkPreview.url] = linkPreview
if unfurlResponse.getProp("statusLinkPreviews", statusLinkPreviewsArr):
for element in statusLinkPreviewsArr.getElems():
let linkPreview = element.toLinkPreview(false)
linkPreviews[linkPreview.url] = linkPreview
for url in requestedUrls:

View File

@ -3,6 +3,11 @@ import core, ../app_service/common/utils
import response_type
import interpret/cropped_image
import ../app_service/service/message/dto/link_preview
import ../app_service/service/message/dto/standard_link_preview
import ../app_service/service/message/dto/status_link_preview
import ../app_service/service/message/dto/status_contact_link_preview
import ../app_service/service/message/dto/status_community_link_preview
import ../app_service/service/message/dto/status_community_channel_link_preview
export response_type
@ -66,6 +71,7 @@ proc sendChatMessage*(
stickerHash: string = "",
stickerPack: string = "0",
): RpcResponse[JsonNode] {.raises: [Exception].} =
let (standardLinkPreviews, statusLinkPreviews) = extractLinkPreviewsLists(linkPreviews)
result = callPrivateRPC("sendChatMessage".prefix, %* [
{
"chatId": chatId,
@ -78,7 +84,8 @@ proc sendChatMessage*(
},
"contentType": contentType,
"communityId": communityId,
"linkPreviews": linkPreviews
"linkPreviews": standardLinkPreviews,
"statusLinkPreviews": statusLinkPreviews
}
])

View File

@ -4,11 +4,121 @@ import QtQuick.Layouts 1.15
import shared.views.chat 1.0
SplitView {
ListModel {
id: mockedLinkPreviewModel
// Create the model dynamically, because `ListElement` doesnt suppport nested elements
Component.onCompleted: {
const emptyObject = {
"unfurled": true,
"immutable": false,
"empty": false,
"url": "https://www.youtube.com/watch?v=9bZkp7q19f0",
"previewType": 1,
"standardPreview": {
"hostname": "www.youtube.com",
"title": "PSY - GANGNAM STYLE(강남스타일) M/V",
"description": "PSY - I LUV IT M/V @ https://youtu.be/Xvjnoagk6GU PSY - New Face M/V @https://youtu.be/OwJPPaEyqhI PSY - 8TH ALBUM '4X2=8' on iTunes @ https://smarturl.it/PSY_8thAlbum PSY - GANGNAM STYLE(강남스타일) on iTunes @ http://smarturl.it/PsyGangnam #PSY #싸이 #GANGNAMSTYLE #강남스타일 More about PSY@ http://www.psyp...",
"linkType": 0,
},
"standardPreviewThumbnail": {
"width": 480,
"height": 360,
"url": "https://i.ytimg.com/vi/9bZkp7q19f0/hqdefault.jpg",
"dataUri": "https://i.ytimg.com/vi/9bZkp7q19f0/hqdefault.jpg",
},
"statusContactPreview": {},
"statusContactPreviewThumbnail": {},
"statusCommunityPreview": {},
"statusCommunityPreviewIcon": {},
"statusCommunityPreviewBanner": {},
"statusCommunityChannelPreview": {},
"statusCommunityChannelCommunityPreview": {},
"statusCommunityChannelCommunityPreviewIcon": {},
"statusCommunityChannelCommunityPreviewBanner": {},
}
const preview1 = Object.assign({}, emptyObject)
preview1.url = "https://www.youtube.com/watch?v=9bZkp7q19f0"
preview1.previewType = 1
preview1.standardPreview = {}
preview1.standardPreviewThumbnail = {}
preview1.standardPreview.hostname = "www.youtube.com"
preview1.standardPreview.title = "PSY - GANGNAM STYLE(강남스타일) M/V"
preview1.standardPreview.description = "PSY - I LUV IT M/V @ https://youtu.be/Xvjnoagk6GU PSY - New Face M/V @https://youtu.be/OwJPPaEyqhI PSY - 8TH ALBUM '4X2=8' on iTunes @ https://smarturl.it/PSY_8thAlbum PSY - GANGNAM STYLE(강남스타일) on iTunes @ http://smarturl.it/PsyGangnam #PSY #싸이 #GANGNAMSTYLE #강남스타일 More about PSY@ http://www.psyp..."
preview1.standardPreview.standardLinkType = 0
preview1.standardPreviewThumbnail.width = 480
preview1.standardPreviewThumbnail.height = 360
preview1.standardPreviewThumbnail.url = "https://i.ytimg.com/vi/9bZkp7q19f0/hqdefault.jpg"
preview1.standardPreviewThumbnail.dataUri = ""
const preview2 = Object.assign({}, emptyObject)
preview2.url = "https://status.app/u/Ow==#zQ3shgmVJjmwwhkfAemjDizYJtv9nzot7QD4iRJ52ZkgdU6Ci"
preview2.previewType = 2
preview2.statusContactPreview = {}
preview2.statusContactPreview.publicKey = "zQ3shgmVJjmwwhkfAemjDizYJtv9nzot7QD4iRJ52ZkgdU6Ci"
preview2.statusContactPreview.displayName = "Test contact display name"
preview2.statusContactPreview.description = "Test description"
preview2.statusContactPreviewThumbnail = {}
preview2.statusContactPreviewThumbnail.width = 64
preview2.statusContactPreviewThumbnail.height = 64
preview2.statusContactPreviewThumbnail.url = "https://placehold.co/64x64"
preview2.statusContactPreviewThumbnail.dataUri = ""
const preview3 = Object.assign({}, emptyObject)
preview3.url = "https://status.app/c/ixiACjAKDlRlc3QgQ29tbXVuaXR5Eg9PcGVuIGZvciBhbnlvbmUYdiIHI0ZGMDAwMCoCHwkD#zQ3shnd55dNx9yTihuL6XMbmyM6UNjzU6jk77h5Js31jxcT5V"
preview3.previewType = 3
preview3.statusCommunityPreview = {}
preview3.statusCommunityPreview.communityId = "zQ3shnd55dNx9yTihuL6XMbmyM6UNjzU6jk77h5Js31jxcT5V"
preview3.statusCommunityPreview.displayName = "Test community display name"
preview3.statusCommunityPreview.description = "Test community description"
preview3.statusCommunityPreview.membersCount = 10
preview3.statusCommunityPreview.color = "#123456"
preview3.statusCommunityPreviewIcon = {}
preview3.statusCommunityPreviewIcon.width = 64
preview3.statusCommunityPreviewIcon.height = 64
preview3.statusCommunityPreviewIcon.url = "https://placehold.co/64x64"
preview3.statusCommunityPreviewIcon.dataUri = ""
preview3.statusCommunityPreviewBanner = {}
preview3.statusCommunityPreviewBanner.width = 320
preview3.statusCommunityPreviewBanner.height = 180
preview3.statusCommunityPreviewBanner.url = "https://placehold.co/320x180"
preview3.statusCommunityPreviewBanner.dataUri = ""
mockedLinkPreviewModel.append(preview1)
mockedLinkPreviewModel.append(preview2)
mockedLinkPreviewModel.append(preview3)
}
}
Pane {
id: messageViewWrapper
SplitView.fillWidth: true
SplitView.fillHeight: true
component LinkPreviewObject: QtObject {
required property string url
required property bool unfurled
required property bool empty
required property int previewType
}
component StandardPreviewObject: QtObject {
required property string hostname
required property string title
required property string description
required property int linkType // 0 = link, 1 = image
}
component ThumbnailObject: QtObject {
required property int width
required property int height
required property string url
required property string dataUri
}
LinksMessageView {
id: linksMessageView
@ -16,57 +126,7 @@ SplitView {
store: {}
messageStore: {}
linkPreviewModel: ListModel {
id: linkPreviewModel
ListElement {
url: "https://www.youtube.com/watch?v=9bZkp7q19f0"
unfurled: true
hostname: "www.youtube.com"
title: "PSY - GANGNAM STYLE(강남스타일) M/V"
description: "PSY - I LUV IT M/V @ https://youtu.be/Xvjnoagk6GU PSY - New Face M/V @https://youtu.be/OwJPPaEyqhI PSY - 8TH ALBUM '4X2=8' on iTunes @ https://smarturl.it/PSY_8thAlbum PSY - GANGNAM STYLE(강남스타일) on iTunes @ http://smarturl.it/PsyGangnam #PSY #싸이 #GANGNAMSTYLE #강남스타일 More about PSY@ http://www.psyp..."
linkType: 0 // 0 = link, 1 = image
thumbnailWidth: 480
thumbnailHeight: 360
thumbnailUrl: "https://i.ytimg.com/vi/9bZkp7q19f0/hqdefault.jpg"
thumbnailDataUri: "https://i.ytimg.com/vi/9bZkp7q19f0/hqdefault.jpg"
}
ListElement {
url: "https://www.youtube.com/watch?v=9bZkp7q19f0"
unfurled: true
hostname: "www.youtube.com"
title: "PSY - GANGNAM STYLE(강남스타일) M/V"
description: "PSY - I LUV IT M/V @ https://youtu.be/Xvjnoagk6GU PSY - New Face M/V @https://youtu.be/OwJPPaEyqhI PSY - 8TH ALBUM '4X2=8' on iTunes @ https://smarturl.it/PSY_8thAlbum PSY - GANGNAM STYLE(강남스타일) on iTunes @ http://smarturl.it/PsyGangnam #PSY #싸이 #GANGNAMSTYLE #강남스타일 More about PSY@ http://www.psyp..."
linkType: 0 // 0 = link, 1 = image
thumbnailWidth: 480
thumbnailHeight: 360
thumbnailUrl: "https://i.ytimg.com/vi/9bZkp7q19f0/hqdefault.jpg"
thumbnailDataUri: "https://i.ytimg.com/vi/9bZkp7q19f0/hqdefault.jpg"
}
ListElement {
url: "https://www.youtube.com/watch?v=9bZkp7q19f0"
unfurled: true
hostname: "www.youtube.com"
title: "PSY - GANGNAM STYLE(강남스타일) M/V"
description: "PSY - I LUV IT M/V @ https://youtu.be/Xvjnoagk6GU PSY - New Face M/V @https://youtu.be/OwJPPaEyqhI PSY - 8TH ALBUM '4X2=8' on iTunes @ https://smarturl.it/PSY_8thAlbum PSY - GANGNAM STYLE(강남스타일) on iTunes @ http://smarturl.it/PsyGangnam #PSY #싸이 #GANGNAMSTYLE #강남스타일 More about PSY@ http://www.psyp..."
linkType: 0 // 0 = link, 1 = image
thumbnailWidth: 480
thumbnailHeight: 360
thumbnailUrl: "https://i.ytimg.com/vi/9bZkp7q19f0/hqdefault.jpg"
thumbnailDataUri: "https://i.ytimg.com/vi/9bZkp7q19f0/hqdefault.jpg"
}
ListElement {
url: "https://www.youtube.com/watch?v=9bZkp7q19f0"
unfurled: true
hostname: "www.youtube.com"
title: "PSY - GANGNAM STYLE(강남스타일) M/V"
description: "PSY - I LUV IT M/V @ https://youtu.be/Xvjnoagk6GU PSY - New Face M/V @https://youtu.be/OwJPPaEyqhI PSY - 8TH ALBUM '4X2=8' on iTunes @ https://smarturl.it/PSY_8thAlbum PSY - GANGNAM STYLE(강남스타일) on iTunes @ http://smarturl.it/PsyGangnam #PSY #싸이 #GANGNAMSTYLE #강남스타일 More about PSY@ http://www.psyp..."
linkType: 0 // 0 = link, 1 = image
thumbnailWidth: 480
thumbnailHeight: 360
thumbnailUrl: "https://i.ytimg.com/vi/9bZkp7q19f0/hqdefault.jpg"
thumbnailDataUri: "https://i.ytimg.com/vi/9bZkp7q19f0/hqdefault.jpg"
}
}
linkPreviewModel: mockedLinkPreviewModel
localUnfurlLinks: {}
isCurrentUser: true

View File

@ -0,0 +1,133 @@
import QtQuick 2.15
import StatusQ.Core 0.1
import utils 1.0
StatusBaseText {
id: root
required property bool unfurled
required property bool empty
required property string url
required property int previewType
required property var standardPreview
required property var standardPreviewThumbnail
required property var statusContactPreview
required property var statusContactPreviewThumbnail
required property var statusCommunityPreview
required property var statusCommunityPreviewIcon
required property var statusCommunityPreviewBanner
required property var statusCommunityChannelPreview
required property var statusCommunityChannelCommunityPreview
required property var statusCommunityChannelCommunityPreviewIcon
required property var statusCommunityChannelCommunityPreviewBanner
wrapMode: Text.WordWrap
function getThumbnailString(thumbnail) {
const thumbnailWidth = thumbnail ? thumbnail.width : ""
const thumbnailHeight = thumbnail ? thumbnail.height : ""
const thumbnailUrl = thumbnail ? thumbnail.url : ""
const thumbnailDataUri = thumbnail ? thumbnail.dataUri : ""
return `(${thumbnailWidth}*${thumbnailHeight}, url: ${thumbnailUrl.length} symbols, data: ${thumbnailDataUri.length} symbols)`
}
function getStandardPreviewString(preview, thumbnail) {
const hostname = standardPreview ? standardPreview.hostname : ""
const title = standardPreview ? standardPreview.title : ""
const description = standardPreview ? standardPreview.description : ""
const thumbnailInfo = getThumbnailString(thumbnail)
return `(hostname: ${hostname}): ${title}\n` +
`description: ${description}\n` +
`thumbnail: ${thumbnailInfo}`
}
function getStatusContactPreviewString(preview, thumbnail) {
const publicKey = preview ? preview.publicKey : ""
const displayName = preview ? preview.displayName : ""
const description = preview ? preview.description : ""
const thumbnailInfo = getThumbnailString(thumbnail)
return `(publicKey: ${publicKey})\n` +
`displayName: ${displayName}\n` +
`description: ${description}\n` +
`icon: ${thumbnailInfo}`
}
function getStatusCommunityPreviewString(preview, icon, banner) {
const communityId = preview ? preview.communityId : ""
const displayName = preview ? preview.displayName : ""
const description = preview ? preview.description : ""
const membersCount = preview ? preview.membersCount : ""
const color = preview ? preview.color : ""
const iconInfo = getThumbnailString(icon)
const bannerInfo = getThumbnailString(banner)
return `communityId: ${communityId}\n` +
`displayName: ${displayName}\n` +
`description: ${description}\n` +
`membersCount: ${membersCount}\n` +
`color: ${color}\n` +
`icon: ${iconInfo}\n` +
`banner: ${bannerInfo}`
}
function getStatusChannelPreviewString(channel, community, communityIcon, communityBanner) {
const channelUuid = channel ? channel.channelUuid : ""
const displayName = channel ? channel.displayName : ""
const description = channel ? channel.description : ""
const emoji = channel ? channel.emoji : ""
const color = channel ? channel.color : ""
const communityInfo = getStatusCommunityPreviewString(community, communityIcon, communityBanner)
return `channelUuid: ${channelUuid}\n` +
`displayName: ${displayName}\n` +
`description: ${description}\n` +
`emoji: ${emoji}\n` +
`color: ${color}\n` +
`- communityInfo: \n${communityInfo}`
}
function linkPreviewTypeString(t) {
switch (t) {
case Constants.LinkPreviewType.NoPreview:
return "NoPreview"
case Constants.LinkPreviewType.Standard:
return "Standard"
case Constants.LinkPreviewType.StatusContact:
return "StatusContact"
case Constants.LinkPreviewType.StatusCommunity:
return "StatusCommunity"
case Constants.LinkPreviewType.StatusCommunityChannel:
return "StatusCommunityChannel"
}
return "???"
}
text: {
const stateEmoji = unfurled ? (empty ? '❌' : '✅') : '👀'
let previewString = ""
switch (previewType) {
case Constants.LinkPreviewType.Standard:
previewString = getStandardPreviewString(standardPreview,
standardPreviewThumbnail)
break
case Constants.LinkPreviewType.StatusContact:
previewString = getStatusContactPreviewString(statusContactPreview,
statusContactPreviewThumbnail)
break
case Constants.LinkPreviewType.StatusCommunity:
previewString = getStatusCommunityPreviewString(statusCommunityPreview,
statusCommunityPreviewIcon,
statusCommunityPreviewBanner)
break
case Constants.LinkPreviewType.StatusCommunityChannel:
previewString = getStatusChannelPreviewString(statusCommunityChannelPreview,
statusCommunityChannelCommunityPreview,
statusCommunityChannelCommunityPreviewIcon,
statusCommunityChannelCommunityPreviewBanner)
break
}
return `${stateEmoji} ${linkPreviewTypeString(previewType)} ${url}\n${previewString}`
}
}

View File

@ -101,32 +101,54 @@ Control {
model: d.filteredModel
delegate: LinkPreviewMiniCard {
// Model properties
required property string title
required property string url
required property bool unfurled
required property bool immutable
required property string hostname
required property string description
required property int linkType
required property int thumbnailWidth
required property int thumbnailHeight
required property string thumbnailUrl
required property string thumbnailDataUri
required property int index
required property bool unfurled
required property bool empty
required property string url
required property bool immutable
required property int previewType
required property var standardPreview
required property var standardPreviewThumbnail
required property var statusContactPreview
required property var statusContactPreviewThumbnail
required property var statusCommunityPreview
required property var statusCommunityPreviewIcon
required property var statusCommunityPreviewBanner
required property var statusCommunityChannelPreview
required property var statusCommunityChannelCommunityPreview
required property var statusCommunityChannelCommunityPreviewIcon
required property var statusCommunityChannelCommunityPreviewBanner
readonly property var thumbnail: {
switch (previewType) {
case Constants.Standard:
return standardPreviewThumbnail
case Constants.StatusContact:
return statusContactPreviewThumbnail
case Constants.StatusCommunity:
return statusCommunityPreviewIcon
case Constants.StatusCommunityChannel:
return statusCommunityChannelCommunityPreviewIcon
}
}
readonly property string thumbnailUrl: thumbnail ? thumbnail.url : ""
readonly property string thumbnailDataUri: thumbnail ? thumbnail.dataUri : ""
Layout.preferredHeight: 64
titleStr: title
domain: hostname //TODO: use domain when available
titleStr: standardPreview ? standardPreview.title : statusContactPreview ? statusContactPreview.displayName : ""
domain: standardPreview ? standardPreview.hostname : "" //TODO: use domain when available
favIconUrl: "" //TODO: use favicon when available
communityName: "" //TODO: add community info when available
channelName: "" //TODO: add community info when available
communityName: statusCommunityPreview ? statusCommunityPreview.displayName : ""
channelName: statusCommunityChannelPreview ? statusCommunityChannelPreview.displayName : ""
thumbnailImageUrl: thumbnailUrl.length > 0 ? thumbnailUrl : thumbnailDataUri
type: linkType === 0 ? LinkPreviewMiniCard.Type.Link : LinkPreviewMiniCard.Type.Image
previewState: unfurled && hostname != "" ? LinkPreviewMiniCard.State.Loaded :
unfurled && hostname === "" ? LinkPreviewMiniCard.State.LoadingFailed :
type: getCardType(previewType, standardPreview)
previewState: unfurled && !empty ? LinkPreviewMiniCard.State.Loaded :
unfurled && empty ? LinkPreviewMiniCard.State.LoadingFailed :
!unfurled ? LinkPreviewMiniCard.State.Loading : LinkPreviewMiniCard.State.Invalid
onClose: root.dismissLinkPreview(d.filteredModel.mapToSource(index))

View File

@ -21,6 +21,7 @@ CalloutCard {
}
enum Type {
Unknown = 0,
Link,
Image,
Community,
@ -28,6 +29,30 @@ CalloutCard {
User
}
function getCardType(previewType, standardLinkPreview) {
switch (previewType) {
case Constants.StatusContact:
return LinkPreviewMiniCard.Type.User
case Constants.StatusCommunity:
return LinkPreviewMiniCard.Type.Community
case Constants.StatusCommunityChannel:
return LinkPreviewMiniCard.Type.Channel
case Constants.Standard:
if (!standardLinkPreview)
return LinkPreviewMiniCard.Type.Unknown
switch (standardLinkPreview.linkType) {
case Constants.StandardLinkPreviewType.Link:
return LinkPreviewMiniCard.Type.Link
case Constants.StandardLinkPreviewType.Image:
return LinkPreviewMiniCard.Type.Image
default:
return LinkPreviewMiniCard.Type.Unknown
}
default:
return LinkPreviewMiniCard.Type.Unknown
}
}
required property string titleStr
required property string domain
required property string communityName

View File

@ -18,6 +18,7 @@ InformationTag 1.0 InformationTag.qml
InformationTile 1.0 InformationTile.qml
Input 1.0 Input.qml
LoadingTokenDelegate 1.0 LoadingTokenDelegate.qml
LinkPreviewDebugView 1.0 LinkPreviewDebugView.qml
RadioButtonSelector 1.0 RadioButtonSelector.qml
RecipientSelector 1.0 RecipientSelector.qml
SearchBox 1.0 SearchBox.qml

View File

@ -7,6 +7,7 @@ import StatusQ.Core.Theme 0.1
import StatusQ.Controls 0.1
import StatusQ.Components 0.1
import shared.controls 1.0
import shared.status 1.0
import shared.panels 1.0
import shared.stores 1.0
@ -89,33 +90,65 @@ Flow {
model: root.linkPreviewModel
delegate: Loader {
id: linkMessageLoader
// properties from the model
required property string url
required property bool unfurled
required property string hostname
required property string title
required property string description
required property int linkType
required property int thumbnailWidth
required property int thumbnailHeight
required property string thumbnailUrl
required property string thumbnailDataUri
required property bool empty
required property string url
required property bool immutable
required property int previewType
required property var standardPreview
required property var standardPreviewThumbnail
required property var statusContactPreview
required property var statusContactPreviewThumbnail
required property var statusCommunityPreview
required property var statusCommunityPreviewIcon
required property var statusCommunityPreviewBanner
required property var statusCommunityChannelPreview
required property var statusCommunityChannelCommunityPreview
required property var statusCommunityChannelCommunityPreviewIcon
required property var statusCommunityChannelCommunityPreviewBanner
readonly property string hostname: standardPreview ? standardPreview.hostname : ""
readonly property string title: standardPreview ? standardPreview.title : ""
readonly property string description: standardPreview ? standardPreview.description : ""
readonly property int standardLinkType: standardPreview ? standardPreview.linkType : ""
readonly property int thumbnailWidth: standardPreviewThumbnail ? standardPreviewThumbnail.width : ""
readonly property int thumbnailHeight: standardPreviewThumbnail ? standardPreviewThumbnail.height : ""
readonly property string thumbnailUrl: standardPreviewThumbnail ? standardPreviewThumbnail.url : ""
readonly property string thumbnailDataUri: standardPreviewThumbnail ? standardPreviewThumbnail.dataUri : ""
property bool animated: false
asynchronous: true
active: unfurled && hostname != ""
active: unfurled && !empty
sourceComponent: LinkPreviewCard {
id: unfurledLink
leftTail: !root.isCurrentUser
StateGroup {
//Using StateGroup as a warkardound for https://bugreports.qt.io/browse/QTBUG-47796
states: [
State {
name: "standardLinkPreview"
when: linkMessageLoader.previewType === Constants.LinkPreviewType.Standard
PropertyChanges { target: linkMessageLoader; sourceComponent: standardLinkPreviewCard }
},
State {
name: "statusContactLinkPreview"
when: linkMessageLoader.previewType === Constants.LinkPreviewType.StatusContact
PropertyChanges { target: linkMessageLoader; sourceComponent: unfurledProfileLinkComponent }
}
]
}
}
}
bannerImageSource: thumbnailUrl.length > 0 ? thumbnailUrl : thumbnailDataUri
title: parent.title
description: parent.description
footer: hostname
onClicked:
(mouse) => {
Component {
id: standardLinkPreviewCard
LinkPreviewCard {
leftTail: !root.isCurrentUser // WARNING: Is this by design?
bannerImageSource: standardPreviewThumbnail ? standardPreviewThumbnail.url : ""
title: standardPreview ? standardPreview.title : ""
description: standardPreview ? standardPreview.description : ""
footer: standardPreview ? standardPreview.hostname : ""
onClicked: (mouse) => {
switch (mouse.button) {
case Qt.RightButton:
root.imageClicked(unfurledLink, mouse, "", url) // request a dumb context menu with just "copy/open link" items
@ -125,8 +158,22 @@ Flow {
break
}
}
highlight: root.highlightLink === url
onHoveredChanged: linksRepeater.hoveredUrl = hovered ? url : ""
}
}
Component {
id: unfurledProfileLinkComponent
UserProfileCard {
id: unfurledProfileLink
leftTail: !root.isCurrentUser
userName: statusContactPreview && statusContactPreview.displayName ? statusContactPreview.displayName : ""
userPublicKey: statusContactPreview && statusContactPreview.publicKey ? statusContactPreview.publicKey : ""
userBio: statusContactPreview && statusContactPreview.description ? statusContactPreview.description : ""
userImage: statusContactPreviewThumbnail ? statusContactPreviewThumbnail.url : ""
ensVerified: false // not supported yet
onClicked: {
Global.openProfilePopup(userPublicKey)
}
}
}
@ -134,15 +181,12 @@ Flow {
//TODO: Remove this once we have gif support in new unfurling flow
Component {
id: unfurledImageComponent
CalloutCard {
implicitWidth: linkImage.width
implicitHeight: linkImage.height
leftTail: !root.isCurrentUser
StatusChatImageLoader {
id: linkImage
readonly property bool globalAnimationEnabled: root.messageStore.playAnimation
readonly property string urlLink: link
property bool localAnimationEnabled: true
@ -195,15 +239,11 @@ Flow {
}
}
}
// Code below can be dropped when New unfurling flow suppports GIFs.
Component {
id: invitationBubble
InvitationBubbleView {
property var invitationData: root.store.getLinkDataForStatusLinks(link)
store: root.store
communityId: invitationData && invitationData.communityData ? invitationData.communityData.communityId : ""
communityData: invitationData && invitationData.communityData ? invitationData.communityData : null
@ -214,7 +254,6 @@ Flow {
if (!invitationData)
linksModel.remove(index)
}
Connections {
enabled: !!invitationData && invitationData.fetching
target: root.store.communitiesModuleInst
@ -225,10 +264,8 @@ Flow {
}
}
}
QtObject {
id: d
readonly property string uuid: Utils.uuid()
readonly property string whiteListedImgExtensions: Constants.acceptedImageExtensions.toString()
readonly property string whiteListedUrls: JSON.stringify(localAccountSensitiveSettings.whitelistedUnfurlingSites)
@ -241,18 +278,14 @@ Flow {
whiteListedImgExtensions,
localAccountSensitiveSettings.displayChatImages)
}
onGetLinkPreviewDataIdChanged: {
linkFetchConnections.enabled = root.localUnfurlLinks !== ""
}
}
Connections {
id: linkFetchConnections
enabled: false
target: root.messageStore.messageModule
function onLinkPreviewDataWasReceived(previewData, uuid) {
if (d.uuid !== uuid)
return
@ -265,12 +298,9 @@ Flow {
}
}
}
ListModel {
id: linksModel
property var rawData
onRawDataChanged: {
linksModel.clear()
rawData.links.forEach((link) => {
@ -279,35 +309,8 @@ Flow {
}
}
Component {
id: unfurledProfileLinkComponent
UserProfileCard {
id: unfurledProfileLink
readonly property var contact: Utils.parseContactUrl(parent.link)
readonly property var contactDetails: Utils.getContactDetailsAsJson(contact.publicKey)
readonly property string nickName: contactDetails ? contactDetails.localNickname : ""
readonly property string ensName: contactDetails ? contactDetails.name : ""
readonly property string displayName: contact && contact.displayName ? contact.displayName :
contactDetails && contactDetails.displayName ? contactDetails.displayName : ""
readonly property string aliasName: contactDetails ? contactDetails.alias : ""
leftTail: !root.isCurrentUser
userName: ProfileUtils.displayName(nickName, ensName, displayName, aliasName)
userPublicKey: contactDetails && contactDetails.publicKey ? contactDetails.publicKey : ""
userBio: contactDetails && contactDetails.bio ? contactDetails.bio : ""
userImage: contactDetails && contactDetails.thumbnailImage ? contactDetails.thumbnailImage : ""
ensVerified: contactDetails && contactDetails.ensVerified ? contactDetails.ensVerified : false
onClicked: {
Global.openProfilePopup(userPublicKey)
}
}
}
Component {
id: enableLinkComponent
Rectangle {
id: enableLinkRoot
width: 300
@ -316,7 +319,6 @@ Flow {
border.width: 1
border.color: Style.current.border
color: Style.current.background
StatusFlatRoundButton {
anchors.top: parent.top
anchors.topMargin: Style.current.smallPadding
@ -327,7 +329,6 @@ Flow {
icon.name: "close-circle"
onClicked: linksModel.remove(index)
}
Image {
id: unfurlingImage
source: Style.png("unfurling-image")
@ -337,7 +338,6 @@ Flow {
anchors.top: parent.top
anchors.topMargin: Style.current.smallPadding
}
StatusBaseText {
id: enableText
text: isImage ? qsTr("Enable automatic image unfurling") :
@ -349,7 +349,6 @@ Flow {
anchors.topMargin: Style.current.halfPadding
color: Theme.palette.directColor1
}
StatusBaseText {
id: infoText
text: qsTr("Once enabled, links posted in the chat may share your metadata with their owners")
@ -360,13 +359,11 @@ Flow {
font.pixelSize: 13
color: Theme.palette.baseColor1
}
Separator {
id: sep1
anchors.top: infoText.bottom
anchors.topMargin: Style.current.smallPadding
}
StatusFlatButton {
id: enableBtn
objectName: "LinksMessageView_enableBtn"
@ -380,19 +377,16 @@ Flow {
background.radius = 0;
}
}
Separator {
id: sep2
anchors.top: enableBtn.bottom
anchors.topMargin: 0
}
Item {
width: parent.width
height: 44
anchors.top: sep2.bottom
clip: true
StatusFlatButton {
id: dontAskBtn
width: parent.width

View File

@ -71,7 +71,7 @@ Loader {
return []
const separator = " "
const arr = links.split(separator)
const filtered = arr.filter(v => v.toLowerCase().endsWith('.gif') || v.toLowerCase().startsWith(Constants.userLinkPrefix.toLowerCase()))
const filtered = arr.filter(v => v.toLowerCase().endsWith('.gif'))
const out = filtered.join(separator)
return out
}

View File

@ -1216,6 +1216,14 @@ QtObject {
}
enum LinkPreviewType {
NoPreview = 0,
Standard = 1,
StatusContact = 2,
StatusCommunity = 3,
StatusCommunityChannel = 4
}
enum StandardLinkPreviewType {
Link = 0,
Image = 1
}

2
vendor/status-go vendored

@ -1 +1 @@
Subproject commit ac813ef5d8f012ba4a0b532483ceeebc553aa3b1
Subproject commit aded258ccb68f88dc995e22f8b4e06157bb642db