feat(SocialLinkPreview): links are too short to display even short urls

- rework the way social links are displayed/editted; we now only allow to
enter a so called "handle" and then substitute that in the final URL
template
- move the "icon" model role to NIM backend

This has several advantages:
- we display only the "handle" and don't have to elide some long URL
- we won't let users enter random URLs into their profile and spoof the
viewing part into clicking it

Additionally, make the social link "button" clickable -> navigate to the
target URL, and make the tooltip behave as "usual" (on mouse hover).
This commit is contained in:
Lukáš Tinkl 2022-12-05 15:21:20 +01:00 committed by Lukáš Tinkl
parent 0a37716c7c
commit e13726e4af
13 changed files with 77 additions and 71 deletions

View File

@ -6,7 +6,7 @@ type
Twitter,
PersonalSite,
Github,
Youtbue,
Youtube,
Discord,
Telegram
@ -15,13 +15,15 @@ type
text*: string
url*: string
linkType: LinkType
icon*: string
proc initSocialLinkItem*(text, url: string, linkType: LinkType): SocialLinkItem =
proc initSocialLinkItem*(text, url: string, linkType: LinkType, icon: string = ""): SocialLinkItem =
result = SocialLinkItem()
result.uuid = $genUUID()
result.text = text
result.url = url
result.linkType = linkType
result.icon = icon
proc uuid*(self: SocialLinkItem): string {.inline.} =
self.uuid

View File

@ -9,11 +9,11 @@ proc toSocialLinkItems*(source: SocialLinks): seq[SocialLinkItem] =
if (text == SOCIAL_LINK_TWITTER_ID): return LinkType.Twitter
if (text == SOCIAL_LINK_PERSONAL_SITE_ID): return LinkType.PersonalSite
if (text == SOCIAL_LINK_GITHUB_ID): return LinkType.Github
if (text == SOCIAL_LINK_YOUTUBE_ID): return LinkType.Youtbue
if (text == SOCIAL_LINK_YOUTUBE_ID): return LinkType.Youtube
if (text == SOCIAL_LINK_DISCORD_ID): return LinkType.Discord
if (text == SOCIAL_LINK_TELEGRAM_ID): return LinkType.Telegram
return LinkType.Custom
result = map(source, x => initSocialLinkItem(x.text, x.url, textToType(x.text)))
result = map(source, x => initSocialLinkItem(x.text, x.url, textToType(x.text), x.icon))
type
ModelRole {.pure.} = enum
@ -21,6 +21,7 @@ type
Text
Url
LinkType
Icon
QtObject:
type
@ -91,6 +92,7 @@ QtObject:
ModelRole.Text.int: "text",
ModelRole.Url.int: "url",
ModelRole.LinkType.int: "linkType",
ModelRole.Icon.int: "icon"
}.toTable
method data(self: SocialLinksModel, index: QModelIndex, role: int): QVariant =
@ -112,3 +114,5 @@ QtObject:
result = newQVariant(item.url)
of ModelRole.LinkType:
result = newQVariant(item.linkType.int)
of ModelRole.Icon:
result = newQVariant(item.icon)

View File

@ -11,13 +11,24 @@ type
SocialLink* = object
text*: string
url*: string
icon*: string
SocialLinks* = seq[SocialLink]
proc socialLinkTextToIcon(text: string): string =
if (text == SOCIAL_LINK_TWITTER_ID): return "twitter"
if (text == SOCIAL_LINK_PERSONAL_SITE_ID): return "language"
if (text == SOCIAL_LINK_GITHUB_ID): return "github"
if (text == SOCIAL_LINK_YOUTUBE_ID): return "youtube"
if (text == SOCIAL_LINK_DISCORD_ID): return "discord"
if (text == SOCIAL_LINK_TELEGRAM_ID): return "telegram"
return ""
proc toSocialLinks*(jsonObj: JsonNode): SocialLinks =
result = map(jsonObj.getElems(),
node => SocialLink(text: node["text"].getStr(),
url: node["url"].getStr())
url: node["url"].getStr(),
icon: socialLinkTextToIcon(node["text"].getStr()))
)
return

View File

@ -66,7 +66,17 @@ SplitView {
verificationStatus: Constants.verificationStatus.unverified,
incomingVerificationStatus: Constants.verificationStatus.unverified,
bio: bio.text,
socialLinks: "" // TODO
socialLinks: JSON.stringify
([{
text: "__twitter",
url: "https://twitter.com/ethstatus",
icon: "twitter"
},
{
text: "__github",
url: "https://github.com/status-im",
icon: "github"
}])
})
}
Component.onCompleted: {

View File

@ -61,9 +61,6 @@ MenuItem {
readonly property bool isImage: d.originalAssetSettings.isImage
readonly property int imgStatus: d.originalAssetSettings.imgStatus
readonly property bool imgIsIdenticon: d.originalAssetSettings.imgIsIdenticon
// crop
readonly property rect cropRect: d.originalAssetSettings.cropRect
}
readonly property StatusFontSettings fontSettings: d.isStatusSubMenu

View File

@ -9,6 +9,7 @@ StatusInput {
id: root
property int linkType
property string icon
leftPadding: 18 // by design
@ -22,15 +23,7 @@ StatusInput {
return ""
}
input.asset {
name: {
if (linkType === Constants.socialLinkType.twitter) return "twitter"
if (linkType === Constants.socialLinkType.personalSite) return "language"
if (linkType === Constants.socialLinkType.github) return "github"
if (linkType === Constants.socialLinkType.youtube) return "youtube"
if (linkType === Constants.socialLinkType.discord) return "discord"
if (linkType === Constants.socialLinkType.telegram) return "telegram"
return ""
}
name: root.icon
width: 20
height: 20
}

View File

@ -71,9 +71,10 @@ Item {
Layout.fillWidth: true
linkType: model.linkType
text: model.url
text: Utils.stripSocialLinkPrefix(model.url, model.linkType)
icon: model.icon
onTextChanged: root.socialLinkChanged(model.uuid, model.text, text)
onTextChanged: root.socialLinkChanged(model.uuid, model.text, Utils.addSocialLinkPrefix(text, model.linkType))
input.tabNavItem: {
if (index < socialLinksRepeater.count - 1) {

View File

@ -81,7 +81,8 @@ StatusDialog {
Layout.fillWidth: true
linkType: model.linkType
text: model.url
text: Utils.stripSocialLinkPrefix(model.url, model.linkType)
icon: model.icon
input.tabNavItem: {
if (index < staticLinksRepeater.count - 1) {
@ -90,7 +91,7 @@ StatusDialog {
return customLinksRepeater.count ? customLinksRepeater.itemAt(0).focusItem : null
}
onTextChanged: root.profileStore.updateLink(model.uuid, model.text, text)
onTextChanged: root.profileStore.updateLink(model.uuid, model.text, Utils.addSocialLinkPrefix(text, linkType))
Component.onCompleted: if (index === 0) {
input.edit.forceActiveFocus()
@ -155,5 +156,4 @@ StatusDialog {
}
}
}
}

View File

@ -13,6 +13,7 @@ Rectangle {
property string text
property string url
property int linkType: 1
property string icon
implicitWidth: layout.implicitWidth + 16
implicitHeight: layout.implicitHeight + 10
@ -32,22 +33,14 @@ Rectangle {
StatusIcon {
Layout.preferredWidth: 20
Layout.preferredHeight: 20
icon: {
if (root.linkType === Constants.socialLinkType.twitter) return "twitter"
if (root.linkType === Constants.socialLinkType.personalSite) return "language"
if (root.linkType === Constants.socialLinkType.github) return "github"
if (root.linkType === Constants.socialLinkType.youtube) return "youtube"
if (root.linkType === Constants.socialLinkType.discord) return "discord"
if (root.linkType === Constants.socialLinkType.telegram) return "telegram"
return ""
}
icon: root.icon
visible: icon !== ""
color: Theme.palette.directColor1
}
StatusBaseText {
Layout.maximumWidth: 150
text: root.linkType === Constants.socialLinkType.custom ? root.text : root.url
text: root.text
color: Theme.palette.directColor4
font.weight: Font.Medium
elide: Text.ElideMiddle
@ -56,43 +49,15 @@ Rectangle {
StatusToolTip {
id: toolTip
contentItem: RowLayout {
StatusBaseText {
Layout.fillHeight: true
Layout.maximumWidth: 300
Layout.bottomMargin: 8
text: toolTip.text
color: Theme.palette.white
elide: Text.ElideMiddle
font.pixelSize: 13
font.weight: Font.Medium
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
}
StatusFlatRoundButton {
Layout.preferredHeight: 24
Layout.preferredWidth: 24
Layout.bottomMargin: 8
icon.name: "copy"
icon.width: 18
icon.height: 18
type: StatusFlatRoundButton.Tertiary
onClicked: {
globalUtils.copyToClipboard(toolTip.text)
toolTip.visible = false
}
}
}
text: root.url
visible: mouseArea.containsMouse
}
MouseArea {
id: mouseArea
anchors.fill: parent
onClicked: toolTip.show(root.url, -1)
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: Global.openLink(root.url)
}
}

View File

@ -45,10 +45,13 @@ Control {
let links = JSON.parse(root.userSocialLinksJson)
for (let i=0; i<links.length; i++) {
let obj = links[i]
const url = obj.url
const type = textToType(obj.text)
socialLinksModel.append({
"text": obj.text,
"url": obj.url,
"linkType": textToType(obj.text)
"text": Utils.stripSocialLinkPrefix(url, type),
"url": url,
"linkType": type,
"icon": obj.icon
})
}
}
@ -131,6 +134,7 @@ Control {
text: model.text
url: model.url
linkType: model.linkType
icon: model.icon
}
}
}

View File

@ -311,9 +311,7 @@ Pane {
else
result = d.contactDetails.optionalName // original display name
}
if (result)
return "(%1)".arg(result)
return ""
return result ? "(%1)".arg(result) : ""
}
visible: text
}

View File

@ -543,6 +543,16 @@ QtObject {
readonly property int telegram: 6
}
readonly property var socialLinkPrefixesByType: [ // NB order must match the "socialLinkType" enum above
"",
"https://twitter.com/",
"",
"https://github.com/",
"https://www.youtube.com/",
"https://discordapp.com/users/",
"https://t.me/"
]
enum DiscordImportErrorCode {
Unknown = 1,
Warning = 2,

View File

@ -756,6 +756,17 @@ QtObject {
return reg.test(msg)
}
function addSocialLinkPrefix(link, type) {
const prefix = Constants.socialLinkPrefixesByType[type]
if (link.startsWith(prefix))
return link
return prefix + link
}
function stripSocialLinkPrefix(link, type) {
return link.replace(Constants.socialLinkPrefixesByType[type], "")
}
// Leave this function at the bottom of the file as QT Creator messes up the code color after this
function isPunct(c) {
return /(!|\@|#|\$|%|\^|&|\*|\(|\)|\+|\||-|=|\\|{|}|[|]|"|;|'|<|>|\?|,|\.|\/)/.test(c)