From b583a4d4bf5c230b0d1d7a3cdc3c763e9fe54507 Mon Sep 17 00:00:00 2001 From: Jonathan Rainville Date: Fri, 23 Oct 2020 15:46:44 -0400 Subject: [PATCH] feat: show unfurled youtube links --- src/app/chat/view.nim | 3 + src/app/chat/views/message_list.nim | 3 + src/status/chat.nim | 3 + src/status/chat/message.nim | 1 + src/status/libstatus/chat.nim | 3 + src/status/signals/messages.nim | 9 ++ .../MessageComponents/CompactMessage.qml | 12 ++ .../MessageComponents/LinksMessage.qml | 146 ++++++++++++++++++ .../MessageComponents/NormalMessage.qml | 15 ++ ui/nim-status-client.pro | 1 + 10 files changed, 196 insertions(+) create mode 100644 ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/LinksMessage.qml diff --git a/src/app/chat/view.nim b/src/app/chat/view.nim index 170df3503d..05466a76c9 100644 --- a/src/app/chat/view.nim +++ b/src/app/chat/view.nim @@ -438,6 +438,9 @@ QtObject: proc copyToClipboard*(self: ChatsView, content: string) {.slot.} = setClipBoardText(content) + proc getLinkPreviewData*(self: ChatsView, link: string): string {.slot.} = + result = $self.status.chat.getLinkPreviewData(link) + proc sendSticker*(self: ChatsView, hash: string, pack: int) {.slot.} = let sticker = Sticker(hash: hash, packId: pack) self.addRecentStickerToList(sticker) diff --git a/src/app/chat/views/message_list.nim b/src/app/chat/views/message_list.nim index 85ae67b358..fa669bc417 100644 --- a/src/app/chat/views/message_list.nim +++ b/src/app/chat/views/message_list.nim @@ -32,6 +32,7 @@ type AudioDurationMs = UserRole + 21 EmojiReactions = UserRole + 22 CommandParameters = UserRole + 23 + LinkUrls = UserRole + 24 QtObject: type @@ -136,6 +137,7 @@ QtObject: of ChatMessageRoles.Audio: result = newQVariant(message.audio) of ChatMessageRoles.AudioDurationMs: result = newQVariant(message.audioDurationMs) of ChatMessageRoles.EmojiReactions: result = newQVariant(self.getReactions(message.id)) + of ChatMessageRoles.LinkUrls: result = newQVariant(message.linkUrls) # Pass the command parameters as a JSON string of ChatMessageRoles.CommandParameters: result = newQVariant($(%*{ "id": message.commandParameters.id, @@ -172,6 +174,7 @@ QtObject: ChatMessageRoles.Audio.int: "audio", ChatMessageRoles.AudioDurationMs.int: "audioDurationMs", ChatMessageRoles.EmojiReactions.int: "emojiReactions", + ChatMessageRoles.LinkUrls.int: "linkUrls", ChatMessageRoles.CommandParameters.int: "commandParameters" }.toTable diff --git a/src/status/chat.nim b/src/status/chat.nim index 6bcfe5f176..c7e1fbb787 100644 --- a/src/status/chat.nim +++ b/src/status/chat.nim @@ -182,6 +182,9 @@ proc clearHistory*(self: ChatModel, chatId: string) = let chat = self.channels[chatId] self.events.emit("chatHistoryCleared", ChannelArgs(chat: chat)) +proc getLinkPreviewData*(self: ChatModel, link: string): JsonNode = + result = status_chat.getLinkPreviewData(link) + proc setActiveChannel*(self: ChatModel, chatId: string) = self.events.emit("activeChannelChanged", ChatIdArg(chatId: chatId)) diff --git a/src/status/chat/message.nim b/src/status/chat/message.nim index 7dda8b1134..ea86f48c0e 100644 --- a/src/status/chat/message.nim +++ b/src/status/chat/message.nim @@ -56,6 +56,7 @@ type Message* = object stickerHash*: string outgoingStatus*: string imageUrls*: string + linkUrls*: string image*: string audio*: string audioDurationMs*: int diff --git a/src/status/libstatus/chat.nim b/src/status/libstatus/chat.nim index 120f7c6726..b96552a750 100644 --- a/src/status/libstatus/chat.nim +++ b/src/status/libstatus/chat.nim @@ -198,3 +198,6 @@ proc muteChat*(chatId: string): string = proc unmuteChat*(chatId: string): string = result = callPrivateRPC("unmuteChat".prefix, %*[chatId]) + +proc getLinkPreviewData*(link: string): JsonNode = + result = callPrivateRPC("getLinkPreviewData".prefix, %*[link]).parseJSON()["result"] \ No newline at end of file diff --git a/src/status/signals/messages.nim b/src/status/signals/messages.nim index 89ef7ef4f9..0746d2c5fd 100644 --- a/src/status/signals/messages.nim +++ b/src/status/signals/messages.nim @@ -1,5 +1,7 @@ import json, random, strutils, sequtils, sugar, chronicles import json_serialization +import ../libstatus/core +import ../libstatus/utils import ../libstatus/accounts as status_accounts import ../libstatus/accounts/constants as constants import ../libstatus/settings as status_settings @@ -187,6 +189,7 @@ proc toMessage*(jsonMsg: JsonNode): Message = stickerHash: "", parsedText: @[], imageUrls: "", + linkUrls: "", image: $jsonMsg{"image"}.getStr, audio: $jsonMsg{"audio"}.getStr, audioDurationMs: jsonMsg{"audioDurationMs"}.getInt, @@ -202,6 +205,12 @@ proc toMessage*(jsonMsg: JsonNode): Message = .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) + .join(" ") + if message.contentType == ContentType.Sticker: message.stickerHash = jsonMsg["sticker"]["hash"].getStr diff --git a/ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/CompactMessage.qml b/ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/CompactMessage.qml index f4b9586a75..168eb3e9d1 100644 --- a/ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/CompactMessage.qml +++ b/ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/CompactMessage.qml @@ -141,6 +141,18 @@ Item { } } + Loader { + id: linksLoader + active: !!linkUrls + anchors.left: chatText.left + anchors.leftMargin: 8 + anchors.top: chatText.bottom + + sourceComponent: Component { + LinksMessage {} + } + } + Loader { id: audioPlayerLoader active: isAudio diff --git a/ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/LinksMessage.qml b/ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/LinksMessage.qml new file mode 100644 index 0000000000..619e58b042 --- /dev/null +++ b/ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/LinksMessage.qml @@ -0,0 +1,146 @@ +import QtQuick 2.3 +import "../../../../../imports" +import "../../../../../shared" + +Item { + id: linksItem + 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 + } + } + return w + } + + Repeater { + id: linksRepeater + model: { + if (!linkUrls) { + return [] + } + + return linkUrls.split(" ") + } + + delegate: Loader { + property string linkString: modelData + active: true + sourceComponent: { + 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 + } + return + }) + + if (linkWhiteListed) { + return unfurledLinkComponent + } + if (linkExists) { + return enableLinkComponent + } + + return + } + } + } + + Component { + id: unfurledLinkComponent + Loader { + property var linkData: { + try { + const data = chatsModel.getLinkPreviewData(linkString) + return JSON.parse(data) + } catch (e) { + console.error("Error parsing link data", e) + return undfined + } + } + enabled: linkData !== undefined && !!linkData.title + sourceComponent: Component { + Rectangle { + id: rectangle + width: 200 + 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: 200 + } + + StyledText { + id: linkTitle + text: linkData.title + elide: Text.ElideRight + anchors.left: parent.left + anchors.right: parent.right + anchors.top: linkImage.bottom + anchors.rightMargin: Style.current.halfPadding + anchors.leftMargin: Style.current.halfPadding + anchors.topMargin: Style.current.halfPadding + } + + StyledText { + id: linkSite + text: linkData.site + color: Style.current.secondaryText + anchors.top: linkTitle.bottom + anchors.topMargin: Style.current.halfPadding + 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) + } + } + } + } + } + + Component { + id: enableLinkComponent + Rectangle { + width: 300 + height: 200 + radius: 16 + + border.width: 1 + border.color: Style.current.border + color:Style.current.background + + StyledText { + text: qsTr("You need to enable this before being able to see it") + } + } + } +} diff --git a/ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/NormalMessage.qml b/ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/NormalMessage.qml index ca0bb76637..f2bedc5af3 100644 --- a/ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/NormalMessage.qml +++ b/ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/NormalMessage.qml @@ -216,6 +216,21 @@ Item { } } + Loader { + id: linksLoader + active: !!linkUrls + anchors.left: !isCurrentUser ? chatImage.right : undefined + anchors.leftMargin: !isCurrentUser ? 8 : 0 + anchors.right: !isCurrentUser ? undefined : parent.right + anchors.rightMargin: !isCurrentUser ? 0 : Style.current.padding + anchors.top: chatBox.bottom + anchors.topMargin: Style.current.smallPadding + + sourceComponent: Component { + LinksMessage {} + } + } + Loader { id: emojiReactionLoader active: emojiReactions !== "" diff --git a/ui/nim-status-client.pro b/ui/nim-status-client.pro index f71dcbb42e..591af68cbd 100644 --- a/ui/nim-status-client.pro +++ b/ui/nim-status-client.pro @@ -148,6 +148,7 @@ DISTFILES += \ 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 \ app/AppLayouts/Chat/ChatColumn/MessageComponents/RectangleCorner.qml \