From 5951fcf131e6f3897fcc985095b7720b4d963b3b Mon Sep 17 00:00:00 2001 From: Jonathan Rainville Date: Wed, 15 Jul 2020 17:04:14 -0400 Subject: [PATCH] feat: refactor Message and add Compact message type --- .../Chat/ChatColumn/ChatMessages.qml | 5 +- ui/app/AppLayouts/Chat/ChatColumn/Message.qml | 562 ++---------------- .../MessageComponents/ChannelIdentifier.qml | 120 ++++ .../MessageComponents/ChatReply.qml | 45 ++ .../ChatColumn/MessageComponents/ChatText.qml | 49 ++ .../ChatColumn/MessageComponents/ChatTime.qml | 18 + .../MessageComponents/CompactMessage.qml | 98 +++ .../MessageComponents/DateGroup.qml | 52 ++ .../MessageComponents/ImageMessage.qml | 61 ++ .../MessageComponents/MessageMouseArea.qml | 29 + .../MessageComponents/NormalMessage.qml | 152 +++++ .../MessageComponents/SentMessage.qml | 15 + .../ChatColumn/MessageComponents/Sticker.qml | 10 + .../MessageComponents/UserImage.qml | 32 + .../MessageComponents/UsernameLabel.qml | 22 + .../Chat/ChatColumn/MessageComponents/qmldir | 1 + ui/app/AppLayouts/Chat/ChatColumn/qmldir | 4 +- .../Profile/Sections/HelpContainer.qml | 15 +- ui/imports/Utils.qml | 14 + ui/nim-status-client.pro | 16 + 20 files changed, 793 insertions(+), 527 deletions(-) create mode 100644 ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/ChannelIdentifier.qml create mode 100644 ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/ChatReply.qml create mode 100644 ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/ChatText.qml create mode 100644 ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/ChatTime.qml create mode 100644 ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/CompactMessage.qml create mode 100644 ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/DateGroup.qml create mode 100644 ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/ImageMessage.qml create mode 100644 ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/MessageMouseArea.qml create mode 100644 ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/NormalMessage.qml create mode 100644 ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/SentMessage.qml create mode 100644 ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/Sticker.qml create mode 100644 ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/UserImage.qml create mode 100644 ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/UsernameLabel.qml create mode 100644 ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/qmldir diff --git a/ui/app/AppLayouts/Chat/ChatColumn/ChatMessages.qml b/ui/app/AppLayouts/Chat/ChatColumn/ChatMessages.qml index 6ea0d86586..12174b49f8 100644 --- a/ui/app/AppLayouts/Chat/ChatColumn/ChatMessages.qml +++ b/ui/app/AppLayouts/Chat/ChatColumn/ChatMessages.qml @@ -122,6 +122,10 @@ ScrollView { } model: messageList + ProfilePopup { + id: profilePopup + } + delegate: Message { id: msgDelegate fromAuthor: model.fromAuthor @@ -152,7 +156,6 @@ ScrollView { scrollToBottom: scrollView.scrollToBottom } } - } /*##^## diff --git a/ui/app/AppLayouts/Chat/ChatColumn/Message.qml b/ui/app/AppLayouts/Chat/ChatColumn/Message.qml index 3021c2ac1f..20b05c7c40 100644 --- a/ui/app/AppLayouts/Chat/ChatColumn/Message.qml +++ b/ui/app/AppLayouts/Chat/ChatColumn/Message.qml @@ -1,11 +1,7 @@ import QtQuick 2.3 -import QtQuick.Controls 2.3 -import QtQuick.Controls 2.12 -import QtQuick.Layouts 1.3 -import Qt.labs.platform 1.1 import "../../../../shared" -import "../../../../shared/xss.js" as XSS import "../../../../imports" +import "./MessageComponents" import "../components" Item { @@ -39,534 +35,78 @@ Item { property var profileClick: function () {} property var scrollToBottom: function () {} property var appSettings + + property bool isCompact: true + + id: messageItem width: parent.width anchors.right: !isCurrentUser ? undefined : parent.right - id: messageWrapper height: { - switch(contentType){ + switch(contentType) { case Constants.chatIdentifier: - return channelIdentifier.height + channelIdentifier.verticalMargin - case Constants.stickerType: - return stickerId.height + 50 + (dateGroupLbl.visible ? 50 : 0) - default: - return childrenRect.height + return childrenRect.height + 50 + default: return childrenRect.height } } - function linkify(inputText) { - // URLs starting with http://, https://, or ftp:// - var replacePattern1 = /(\b(https?|ftp):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/gim; - var replacedText = inputText.replace(replacePattern1, "$1"); - - // URLs starting with "www." (without // before it, or it'd re-link the ones done above). - var replacePattern2 = /(^|[^\/])(www\.[\S]+(\b|$))/gim; - replacedText = replacedText.replace(replacePattern2, "$1$2"); - - replacedText = XSS.filterXSS(replacedText) - return replacedText; + function clickMessage() { + SelectedMessage.set(messageId, fromAuthor); + profileClick(userName, fromAuthor, identicon); + messageContextMenu.popup() } - ProfilePopup { - id: profilePopup + Loader { + active :true + width: parent.width + // TODO get prop from settings + sourceComponent: { + switch(contentType) { + case Constants.chatIdentifier: + return channelIdentifierComponent + case Constants.systemMessagePrivateGroupType: + return channelIdentifierComponent + default: + return isCompact ? compactMessageComponent : messageComponent + } + } } - Item { - property int verticalMargin: 50 - id: channelIdentifier - visible: authorCurrentMsg == "" - anchors.horizontalCenter: parent.horizontalCenter - anchors.top: parent.top - anchors.topMargin: this.visible ? verticalMargin : 0 - height: this.visible ? childrenRect.height + verticalMargin : 0 - - Rectangle { - id: circleId - anchors.horizontalCenter: parent.horizontalCenter - width: 120 - height: 120 - radius: 120 - border.width: chatsModel.activeChannel.chatType == Constants.chatTypeOneToOne ? 2 : 0 - border.color: Style.current.grey - color: { - if (chatsModel.activeChannel.chatType == Constants.chatTypeOneToOne) { - return Style.current.transparent - } - return chatsModel.activeChannel.color - } - - Image { - visible: chatsModel.activeChannel.chatType == Constants.chatTypeOneToOne - anchors.horizontalCenter: parent.horizontalCenter - anchors.verticalCenter: parent.verticalCenter - width: 120 - height: 120 - fillMode: Image.PreserveAspectFit - source: chatsModel.activeChannel.identicon - mipmap: true - smooth: false - antialiasing: true - } - - StyledText { - visible: chatsModel.activeChannel.chatType != Constants.chatTypeOneToOne - text: (chatsModel.activeChannel.name.charAt(0) == "#" ? chatsModel.activeChannel.name.charAt(1) : chatsModel.activeChannel.name.charAt(0)).toUpperCase() - opacity: 0.7 - font.weight: Font.Bold - font.pixelSize: 51 - color: "white" - anchors.horizontalCenter: parent.horizontalCenter - anchors.verticalCenter: parent.verticalCenter - } - } - - StyledText { - id: channelName - wrapMode: Text.Wrap - text: { - if (chatsModel.activeChannel.chatType != Constants.chatTypePublic) { - return chatsModel.activeChannel.name; - } else { - return "#" + chatsModel.activeChannel.name; - } - } - font.weight: Font.Bold - font.pixelSize: 22 - color: Style.current.textColor - anchors.top: circleId.bottom - anchors.topMargin: 16 - anchors.horizontalCenter: parent.horizontalCenter - } - - Item { - visible: chatsModel.activeChannel.chatType == Constants.chatTypePrivateGroupChat && !chatsModel.activeChannel.isMember(profileModel.profile.pubKey) - anchors.top: channelName.bottom - anchors.topMargin: 16 - id: joinOrDecline - - StyledText { - id: joinChat - //% "Join chat" - text: qsTrId("join-chat") - font.pixelSize: 20 - color: Style.current.blue - anchors.horizontalCenter: parent.horizontalCenter - - MouseArea { - cursorShape: Qt.PointingHandCursor - anchors.fill: parent - onClicked: { - chatsModel.joinGroup() - } - } - } - - StyledText { - //% "Decline invitation" - text: qsTrId("group-chat-decline-invitation") - font.pixelSize: 20 - color: Style.current.blue - anchors.horizontalCenter: parent.horizontalCenter - anchors.top: joinChat.bottom - anchors.topMargin: Style.current.padding - MouseArea { - cursorShape: Qt.PointingHandCursor - anchors.fill: parent - onClicked: { - chatsModel.leaveActiveChat() - } - } - } + Component { + id: channelIdentifierComponent + ChannelIdentifier { + authorCurrentMsg: messageItem.authorCurrentMsg } } // Private group Messages - StyledText { - wrapMode: Text.Wrap - text: message - visible: isStatusMessage - font.pixelSize: 16 - color: Style.current.darkGrey - width: parent.width - 120 - horizontalAlignment: Text.AlignHCenter - anchors.horizontalCenter: parent.horizontalCenter - textFormat: Text.RichText - } - - StyledText { - id: dateGroupLbl - font.pixelSize: 13 - color: Style.current.darkGrey - horizontalAlignment: Text.AlignHCenter - anchors.horizontalCenter: parent.horizontalCenter - text: { - if (prevMessageIndex == -1) return ""; // identifier - - let now = new Date() - let yesterday = new Date() - yesterday.setDate(now.getDate()-1) - - let prevMsgTimestamp = chatsModel.messageList.getMessageData(prevMessageIndex, "timestamp") - var currentMsgDate = new Date(parseInt(timestamp, 10)); - var prevMsgDate = prevMsgTimestamp === "" ? new Date(0) : new Date(parseInt(prevMsgTimestamp, 10)); - if(currentMsgDate.getDay() !== prevMsgDate.getDay()){ - if (now.toDateString() === currentMsgDate.toDateString()) { - return qsTr("Today") - } else if (yesterday.toDateString() === currentMsgDate.toDateString()) { - //% "Yesterday" - return qsTrId("yesterday") - } else { - const monthNames = [ - qsTr("January"), - qsTr("February"), - qsTr("March"), - qsTr("April"), - qsTr("May"), - qsTr("June"), - qsTr("July"), - qsTr("August"), - qsTr("September"), - qsTr("October"), - qsTr("November"), - qsTr("December") - ]; - return monthNames[currentMsgDate.getMonth()] + ", " + currentMsgDate.getDay() - } - } else { - return ""; - } - - } - anchors.top: parent.top - anchors.topMargin: 20 - visible: text !== "" - } - - // Messages - Image { - id: chatImage - width: 36 - height: 36 - anchors.left: parent.left - anchors.leftMargin: Style.current.padding - anchors.top: dateGroupLbl.visible ? dateGroupLbl.bottom : parent.top - anchors.topMargin: 20 - fillMode: Image.PreserveAspectFit - source: identicon - visible: (isMessage || isEmoji) && authorCurrentMsg != authorPrevMsg && !isCurrentUser - mipmap: true - smooth: false - antialiasing: true - - MouseArea { - cursorShape: Qt.PointingHandCursor - anchors.fill: parent - onClicked: { - SelectedMessage.set(messageId, fromAuthor); - profileClick(userName, fromAuthor, identicon); - messageContextMenu.popup() - } - } - } - - StyledTextEdit { - id: chatName - text: userName - anchors.leftMargin: 20 - anchors.top: dateGroupLbl.visible ? dateGroupLbl.bottom : parent.top - anchors.topMargin: 0 - anchors.left: chatImage.right - font.bold: true - font.pixelSize: 14 - readOnly: true - wrapMode: Text.WordWrap - selectByMouse: true - visible: (isMessage || isEmoji) && authorCurrentMsg != authorPrevMsg && !isCurrentUser - MouseArea { - cursorShape: Qt.PointingHandCursor - anchors.fill: parent - onClicked: { - SelectedMessage.set(messageId, fromAuthor); - profileClick(userName, fromAuthor, identicon) - messageContextMenu.popup() - } - } - } - - Rectangle { - property int chatVerticalPadding: 7 - property int chatHorizontalPadding: 12 - - id: chatBox - color: isSticker ? Style.current.background : (isCurrentUser ? Style.current.blue : Style.current.secondaryBackground) - border.color: isSticker ? Style.current.border : Style.current.transparent - border.width: 1 - height: (3 * chatVerticalPadding) + (contentType == Constants.stickerType ? stickerId.height : (chatText.height + chatReply.height)) - width: { - switch(contentType){ - case Constants.stickerType: - return stickerId.width + (2 * chatBox.chatHorizontalPadding); - default: - return plainText.length > 54 ? 400 : chatText.width + 2 * chatHorizontalPadding - } - } - - radius: 16 - 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: authorCurrentMsg != authorPrevMsg && !isCurrentUser ? chatImage.top : (dateGroupLbl.visible ? dateGroupLbl.bottom : parent.top) - anchors.topMargin: 0 - visible: isMessage || isEmoji - - Rectangle { - id: chatReply - color: isCurrentUser ? Style.current.blue : Style.current.lightBlue - visible: responseTo != "" && replyMessageIndex > -1 - height: chatReply.visible ? childrenRect.height : 0 - anchors.top: parent.top - anchors.topMargin: chatReply.visible ? chatBox.chatVerticalPadding : 0 - anchors.left: parent.left - anchors.leftMargin: Style.current.padding - anchors.right: parent.right - anchors.rightMargin: chatBox.chatHorizontalPadding - - StyledTextEdit { - id: lblReplyAuthor - text: "↳" + repliedMessageAuthor - color: Style.current.darkGrey - readOnly: true - selectByMouse: true - wrapMode: Text.Wrap - anchors.left: parent.left - anchors.right: parent.right - } - - StyledTextEdit { - id: lblReplyMessage - anchors.top: lblReplyAuthor.bottom - anchors.topMargin: 5 - text: Emoji.parse(linkify(repliedMessageContent), "26x26"); - textFormat: Text.RichText - color: Style.current.darkGrey - readOnly: true - selectByMouse: true - wrapMode: Text.Wrap - anchors.left: parent.left - anchors.right: parent.right - } - - Separator { - anchors.top: lblReplyMessage.bottom - anchors.topMargin: 8 - anchors.left: lblReplyMessage.left - anchors.right: lblReplyMessage.right - anchors.rightMargin: chatBox.chatHorizontalPadding - color: Style.current.darkGrey - } - } - - StyledTextEdit { - id: chatText - textFormat: Text.RichText - text: { - if(contentType === Constants.stickerType) return ""; - let msg = linkify(message); - if(isEmoji){ - return Emoji.parse(msg, "72x72"); - } else { - return ` - - - - - ${Emoji.parse(msg, "26x26")} - - `; - } - - } - anchors.left: parent.left - anchors.leftMargin: parent.chatHorizontalPadding - anchors.right: plainText.length > 52 ? parent.right : undefined - anchors.rightMargin: plainText.length > 52 ? parent.chatHorizontalPadding : 0 - horizontalAlignment: !isCurrentUser ? Text.AlignLeft : Text.AlignRight + Component { + id: privateGroupHeaderComponent + StyledText { wrapMode: Text.Wrap - anchors.top: chatReply.bottom - anchors.topMargin: chatBox.chatVerticalPadding - font.pixelSize: 15 - readOnly: true - selectByMouse: true - color: !isCurrentUser ? Style.current.textColor : Style.current.currentUserTextColor - visible: contentType == Constants.messageType || isEmoji - } - - Image { - id: stickerId - horizontalAlignment: !isCurrentUser ? Text.AlignLeft : Text.AlignRight - anchors.left: parent.left - anchors.leftMargin: parent.chatHorizontalPadding - anchors.top: parent.top - anchors.topMargin: chatBox.chatVerticalPadding - width: 140 - height: 140 - source: contentType === Constants.stickerType ? ("https://ipfs.infura.io/ipfs/" + sticker) : "" - visible: contentType === Constants.stickerType - } - - MouseArea { - anchors.fill: parent - cursorShape: chatText.hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor - acceptedButtons: Qt.LeftButton | Qt.RightButton - onClicked: { - if(mouse.button & Qt.RightButton) { - SelectedMessage.set(messageId, fromAuthor); - profileClick(userName, fromAuthor, identicon); - messageContextMenu.popup() - return; - } - - let link = chatText.hoveredLink; - if(link.startsWith("#")){ - chatsModel.joinChat(link.substring(1), Constants.chatTypePublic); - return; - } - - if (link.startsWith('//')) { - let pk = link.replace("//", ""); - profileClick(chatsModel.userNameOrAlias(pk), pk, chatsModel.generateIdenticon(pk)) - return; - } - - Qt.openUrlExternally(link) - } + text: message + visible: isStatusMessage + font.pixelSize: 16 + color: Style.current.darkGrey + width: parent.width - 120 + horizontalAlignment: Text.AlignHCenter + anchors.horizontalCenter: parent.horizontalCenter + textFormat: Text.RichText } } - StyledTextEdit { - id: chatTime - color: Style.current.darkGrey - text: { - let messageDate = new Date(Math.floor(timestamp)) - let minutes = messageDate.getMinutes(); - let hours = messageDate.getHours(); - return (hours < 10 ? "0" + hours : hours) + ":" + (minutes < 10 ? "0" + minutes : minutes) + // Normal message + Component { + id: messageComponent + NormalMessage { + clickMessage: messageItem.clickMessage } - anchors.top: messageWrapper.appSettings.displayChatImages && imageUrls != "" ? imageChatBox.bottom : chatBox.bottom - anchors.topMargin: 4 - anchors.bottomMargin: Style.current.padding - anchors.right: messageWrapper.appSettings.displayChatImages && imageUrls != "" ? imageChatBox.right : chatBox.right - anchors.rightMargin: isCurrentUser ? 5 : Style.current.padding - font.pixelSize: 10 - readOnly: true - selectByMouse: true - visible: (isEmoji || isMessage || isSticker) - } - - StyledTextEdit { - id: sentMessage - color: Style.current.darkGrey - text: outgoingStatus == "sent" ? - //% "Sent" - qsTrId("status-sent") : - //% "Sending..." - qsTrId("sending") - anchors.top: chatTime.top - anchors.bottomMargin: Style.current.padding - anchors.right: chatTime.left - anchors.rightMargin: 5 - font.pixelSize: 10 - readOnly: true - visible: isCurrentUser && (isEmoji || isMessage || isSticker) } - Rectangle { - property int chatVerticalPadding: 12 - property int chatHorizontalPadding: 12 - property int imageWidth: 350 - - id: imageChatBox - visible: messageWrapper.appSettings.displayChatImages && imageUrls != "" - height: { - if (!imageChatBox.visible) { - return 0 - } - - let h = chatVerticalPadding - for (let i = 0; i < imageRepeater.count; i++) { - h += imageRepeater.itemAt(i).height - } - return h + chatVerticalPadding * imageRepeater.count - } - color: isCurrentUser ? Style.current.blue : Style.current.lightBlue - border.color: "transparent" - width: imageWidth+ 2 * chatHorizontalPadding - radius: 16 - 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: messageWrapper.appSettings.displayChatImages && imageUrls != "" ? chatBox.bottom : chatTime.bottom - anchors.topMargin: Style.current.smallPadding - - Repeater { - id: imageRepeater - model: messageWrapper.appSettings.displayChatImages && imageUrls != "" ? imageUrls.split(" ") : [] - - Image { - id: imageMessage - anchors.horizontalCenter: parent.horizontalCenter - anchors.top: (index == 0) ? parent.top: parent.children[index-1].bottom - anchors.topMargin: imageChatBox.chatVerticalPadding - sourceSize.width: imageChatBox.imageWidth - source: modelData - onStatusChanged: { - if (imageMessage.status == Image.Error) { - imageMessage.height = 0 - imageMessage.visible = false - imageChatBox.height = 0 - imageChatBox.visible = false - } else if (imageMessage.status == Image.Ready) { - messageWrapper.scrollToBottom(true, messageWrapper) - } - } - } - } - - // This rectangle's only job is to mask the corner to make it less rounded... yep - Rectangle { - color: parent.color - width: 18 - height: 18 - anchors.bottom: parent.bottom - anchors.bottomMargin: 0 - anchors.left: !isCurrentUser ? parent.left : undefined - anchors.leftMargin: 0 - anchors.right: !isCurrentUser ? undefined : parent.right - anchors.rightMargin: 0 - radius: 4 - z: -1 + // Compact Messages + Component { + id: compactMessageComponent + CompactMessage { + clickMessage: messageItem.clickMessage } } } diff --git a/ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/ChannelIdentifier.qml b/ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/ChannelIdentifier.qml new file mode 100644 index 0000000000..c2a47ce60b --- /dev/null +++ b/ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/ChannelIdentifier.qml @@ -0,0 +1,120 @@ +import QtQuick 2.3 +import "../../../../../shared" +import "../../../../../imports" + +Item { + property string authorCurrentMsg: "authorCurrentMsg" + property int verticalMargin: 50 + + id: channelIdentifier + visible: authorCurrentMsg == "" + anchors.horizontalCenter: parent.horizontalCenter + anchors.top: parent.top + anchors.topMargin: this.visible ? verticalMargin : 0 + height: this.visible ? childrenRect.height + verticalMargin : 0 + + Rectangle { + id: circleId + anchors.horizontalCenter: parent.horizontalCenter + width: 120 + height: 120 + radius: 120 + border.width: chatsModel.activeChannel.chatType === Constants.chatTypeOneToOne ? 2 : 0 + border.color: Style.current.grey + color: { + if (chatsModel.activeChannel.chatType === Constants.chatTypeOneToOne) { + return Style.current.transparent + } + return chatsModel.activeChannel.color + } + + Image { + visible: chatsModel.activeChannel.chatType === Constants.chatTypeOneToOne + anchors.horizontalCenter: parent.horizontalCenter + anchors.verticalCenter: parent.verticalCenter + width: 120 + height: 120 + fillMode: Image.PreserveAspectFit + source: chatsModel.activeChannel.identicon + mipmap: true + smooth: false + antialiasing: true + } + + StyledText { + visible: chatsModel.activeChannel.chatType !== Constants.chatTypeOneToOne + text: (chatsModel.activeChannel.name.charAt(0) === "#" ? chatsModel.activeChannel.name.charAt(1) : chatsModel.activeChannel.name.charAt(0)).toUpperCase() + opacity: 0.7 + font.weight: Font.Bold + font.pixelSize: 51 + color: Style.current.white + anchors.horizontalCenter: parent.horizontalCenter + anchors.verticalCenter: parent.verticalCenter + } + } + + StyledText { + id: channelName + wrapMode: Text.Wrap + text: { + if (chatsModel.activeChannel.chatType !== Constants.chatTypePublic) { + return chatsModel.activeChannel.name; + } else { + return "#" + chatsModel.activeChannel.name; + } + } + font.weight: Font.Bold + font.pixelSize: 22 + color: Style.current.black + anchors.top: circleId.bottom + anchors.topMargin: 16 + anchors.horizontalCenter: parent.horizontalCenter + } + + Item { + visible: chatsModel.activeChannel.chatType === Constants.chatTypePrivateGroupChat && !chatsModel.activeChannel.isMember(profileModel.profile.pubKey) + anchors.top: channelName.bottom + anchors.topMargin: 16 + id: joinOrDecline + + StyledText { + id: joinChat + //% "Join chat" + text: qsTrId("join-chat") + font.pixelSize: 20 + color: Style.current.blue + anchors.horizontalCenter: parent.horizontalCenter + + MouseArea { + cursorShape: Qt.PointingHandCursor + anchors.fill: parent + onClicked: { + chatsModel.joinGroup() + } + } + } + + StyledText { + //% "Decline invitation" + text: qsTrId("group-chat-decline-invitation") + font.pixelSize: 20 + color: Style.current.blue + anchors.horizontalCenter: parent.horizontalCenter + anchors.top: joinChat.bottom + anchors.topMargin: Style.current.padding + MouseArea { + cursorShape: Qt.PointingHandCursor + anchors.fill: parent + onClicked: { + chatsModel.leaveActiveChat() + } + } + } + } +} + +/*##^## +Designer { + D{i:0;autoSize:true;formeditorZoom:0.5;height:480;width:640} +} +##^##*/ diff --git a/ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/ChatReply.qml b/ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/ChatReply.qml new file mode 100644 index 0000000000..89ed93b987 --- /dev/null +++ b/ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/ChatReply.qml @@ -0,0 +1,45 @@ +import QtQuick 2.3 +import "../../../../../shared" +import "../../../../../imports" + +Rectangle { + id: chatReply + color: Style.current.lightBlue + visible: responseTo != "" && replyMessageIndex > -1 + // childrenRect.height shows a binding loop for soem reason, so we use heights instead + height: this.visible ? lblReplyAuthor.height + lblReplyMessage.height + 5 + 8 : 0 + + StyledTextEdit { + id: lblReplyAuthor + text: "↳" + repliedMessageAuthor + color: Style.current.darkGrey + readOnly: true + selectByMouse: true + wrapMode: Text.Wrap + anchors.left: parent.left + anchors.right: parent.right + } + + StyledTextEdit { + id: lblReplyMessage + anchors.top: lblReplyAuthor.bottom + anchors.topMargin: 5 + text: Emoji.parse(Utils.linkifyAndXSS(repliedMessageContent), "26x26"); + textFormat: Text.RichText + color: Style.current.darkGrey + readOnly: true + selectByMouse: true + wrapMode: Text.Wrap + anchors.left: parent.left + anchors.right: parent.right + } + + Separator { + anchors.top: lblReplyMessage.bottom + anchors.topMargin: 8 + anchors.left: lblReplyMessage.left + anchors.right: lblReplyMessage.right + anchors.rightMargin: chatTextItem.chatHorizontalPadding + color: Style.current.darkGrey + } +} diff --git a/ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/ChatText.qml b/ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/ChatText.qml new file mode 100644 index 0000000000..850d9d329f --- /dev/null +++ b/ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/ChatText.qml @@ -0,0 +1,49 @@ +import QtQuick 2.3 +import "../../../../../shared" +import "../../../../../imports" + +StyledTextEdit { + id: chatText + visible: contentType == Constants.messageType || isEmoji + textFormat: Text.RichText + text: { + if(contentType === Constants.stickerType) return ""; + let msg = Utils.linkifyAndXSS(message); + if(isEmoji){ + return Emoji.parse(msg, "72x72"); + } else { + return ` + + + + + ${Emoji.parse(msg, "26x26")} + + `; + } + + } + horizontalAlignment: Text.AlignLeft + wrapMode: Text.Wrap + font.pixelSize: 15 + readOnly: true + selectByMouse: true + color: Style.current.textColor +} diff --git a/ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/ChatTime.qml b/ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/ChatTime.qml new file mode 100644 index 0000000000..bfc0e286c9 --- /dev/null +++ b/ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/ChatTime.qml @@ -0,0 +1,18 @@ +import QtQuick 2.3 +import "../../../../../shared" +import "../../../../../imports" + +StyledTextEdit { + id: chatTime + visible: (isEmoji || isMessage || isSticker) + color: Style.current.darkGrey + text: { + let messageDate = new Date(Math.floor(timestamp)) + let minutes = messageDate.getMinutes(); + let hours = messageDate.getHours(); + return (hours < 10 ? "0" + hours : hours) + ":" + (minutes < 10 ? "0" + minutes : minutes) + } + font.pixelSize: 10 + readOnly: true + selectByMouse: true +} diff --git a/ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/CompactMessage.qml b/ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/CompactMessage.qml new file mode 100644 index 0000000000..b28680d5fd --- /dev/null +++ b/ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/CompactMessage.qml @@ -0,0 +1,98 @@ +import QtQuick 2.3 +import "../../../../../shared" +import "../../../../../imports" + +Item { + property var clickMessage: function () {} + property int chatHorizontalPadding: 12 + property int chatVerticalPadding: 7 + + id: chatTextItem + anchors.top: parent.top + anchors.topMargin: authorCurrentMsg != authorPrevMsg ? Style.current.smallPadding : 0 + height: childrenRect.height + this.anchors.topMargin + width: parent.width + + + // FIXME @jonathanr: Adding this breaks the first line. Need to fix the height somehow +// DateGroup { +// id: dateGroupLbl +// } + + UserImage { + id: chatImage + anchors.left: parent.left + anchors.leftMargin: Style.current.padding +// anchors.top: dateGroupLbl.visible ? dateGroupLbl.bottom : parent.top + anchors.top: parent.top + } + + UsernameLabel { + id: chatName + anchors.leftMargin: chatTextItem.chatHorizontalPadding +// anchors.top: dateGroupLbl.visible ? dateGroupLbl.bottom : parent.top + anchors.top: parent.top + anchors.left: chatImage.right + } + + ChatReply { + id: chatReply +// anchors.top: chatName.visible ? chatName.bottom : (dateGroupLbl.visible ? dateGroupLbl.bottom : parent.top) + anchors.top: chatName.visible ? chatName.bottom : parent.top + anchors.topMargin: chatName.visible && this.visible ? chatTextItem.chatVerticalPadding : 0 + anchors.left: chatImage.right + anchors.leftMargin: chatTextItem.chatHorizontalPadding + anchors.right: parent.right + anchors.rightMargin: chatTextItem.chatHorizontalPadding + } + + ChatText { + id: chatText + anchors.top: chatReply.bottom + anchors.topMargin: chatName.visible && this.visible ? chatTextItem.chatVerticalPadding : 0 + anchors.left: chatImage.right + anchors.leftMargin: chatTextItem.chatHorizontalPadding + anchors.right: parent.right + anchors.rightMargin: chatTextItem.chatHorizontalPadding + } + + Rectangle { + id: stickerContainer + visible: contentType === Constants.stickerType + color: Style.current.transparent + border.color: Style.current.grey + border.width: 1 + radius: 16 + width: stickerId.width + height: stickerId.height + anchors.left: chatText.left + anchors.top: chatName.visible ? chatName.bottom : parent.top + anchors.topMargin: this.visible && chatName.visible ? chatTextItem.chatVerticalPadding : 0 + + Sticker { + id: stickerId + visible: stickerContainer.visible + } + } + + MessageMouseArea { + anchors.fill: stickerContainer.visible ? stickerContainer : chatText + } + + // TODO show date for not the first messsage (on hover maybe) + ChatTime { + id: chatTime + visible: authorCurrentMsg != authorPrevMsg + anchors.verticalCenter: chatName.verticalCenter + anchors.left: chatName.right + anchors.leftMargin: Style.current.padding + } + + SentMessage { + id: sentMessage + visible: isCurrentUser && outgoingStatus != "sent" + anchors.verticalCenter: chatTime.verticalCenter + anchors.left: chatTime.right + anchors.rightMargin: 5 + } +} diff --git a/ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/DateGroup.qml b/ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/DateGroup.qml new file mode 100644 index 0000000000..c095f46d2e --- /dev/null +++ b/ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/DateGroup.qml @@ -0,0 +1,52 @@ +import QtQuick 2.3 +import "../../../../../shared" +import "../../../../../imports" + +StyledText { + id: dateGroupLbl + font.pixelSize: 13 + color: Style.current.darkGrey + horizontalAlignment: Text.AlignHCenter + anchors.horizontalCenter: parent.horizontalCenter + text: { + if (prevMessageIndex == -1) return ""; // identifier + + let now = new Date() + let yesterday = new Date() + yesterday.setDate(now.getDate()-1) + + let prevMsgTimestamp = chatsModel.messageList.getMessageData(prevMessageIndex, "timestamp") + var currentMsgDate = new Date(parseInt(timestamp, 10)); + var prevMsgDate = prevMsgTimestamp === "" ? new Date(0) : new Date(parseInt(prevMsgTimestamp, 10)); + if(currentMsgDate.getDay() !== prevMsgDate.getDay()){ + if (now.toDateString() === currentMsgDate.toDateString()) { + return qsTr("Today") + } else if (yesterday.toDateString() === currentMsgDate.toDateString()) { + //% "Yesterday" + return qsTrId("yesterday") + } else { + const monthNames = [ + qsTr("January"), + qsTr("February"), + qsTr("March"), + qsTr("April"), + qsTr("May"), + qsTr("June"), + qsTr("July"), + qsTr("August"), + qsTr("September"), + qsTr("October"), + qsTr("November"), + qsTr("December") + ]; + return monthNames[currentMsgDate.getMonth()] + ", " + currentMsgDate.getDay() + } + } else { + return ""; + } + + } + visible: text !== "" + anchors.top: parent.top + anchors.topMargin: this.visible ? 20 : 0 +} diff --git a/ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/ImageMessage.qml b/ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/ImageMessage.qml new file mode 100644 index 0000000000..31fb329b4c --- /dev/null +++ b/ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/ImageMessage.qml @@ -0,0 +1,61 @@ +import QtQuick 2.3 +import "../../../../../shared" +import "../../../../../imports" + +Rectangle { + property int chatVerticalPadding: 12 + property int chatHorizontalPadding: 12 + property int imageWidth: 350 + + id: imageChatBox + height: { + let h = chatVerticalPadding + for (let i = 0; i < imageRepeater.count; i++) { + h += imageRepeater.itemAt(i).height + } + return h + chatVerticalPadding * imageRepeater.count + } + color: isCurrentUser ? Style.current.blue : Style.current.lightBlue + border.color: "transparent" + width: imageWidth + 2 * chatHorizontalPadding + radius: 16 + + Repeater { + id: imageRepeater + model: imageUrls.split(" ") + + Image { + id: imageMessage + anchors.horizontalCenter: parent.horizontalCenter + anchors.top: (index == 0) ? parent.top: parent.children[index-1].bottom + anchors.topMargin: imageChatBox.chatVerticalPadding + sourceSize.width: imageChatBox.imageWidth + source: modelData + onStatusChanged: { + if (imageMessage.status == Image.Error) { + imageMessage.height = 0 + imageMessage.visible = false + imageChatBox.height = 0 + imageChatBox.visible = false + } else if (imageMessage.status == Image.Ready) { + messageItem.scrollToBottom(true, messageItem) + } + } + } + } + + // This rectangle's only job is to mask the corner to make it less rounded... yep + Rectangle { + color: parent.color + width: 18 + height: 18 + anchors.bottom: parent.bottom + anchors.bottomMargin: 0 + anchors.left: !isCurrentUser ? parent.left : undefined + anchors.leftMargin: 0 + anchors.right: !isCurrentUser ? undefined : parent.right + anchors.rightMargin: 0 + radius: 4 + z: -1 + } +} diff --git a/ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/MessageMouseArea.qml b/ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/MessageMouseArea.qml new file mode 100644 index 0000000000..8d85da3730 --- /dev/null +++ b/ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/MessageMouseArea.qml @@ -0,0 +1,29 @@ +import QtQuick 2.3 +import "../../../../../shared" +import "../../../../../imports" + +MouseArea { + cursorShape: chatText.hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor + acceptedButtons: Qt.LeftButton | Qt.RightButton + onClicked: { + if(mouse.button & Qt.RightButton) { + clickMessage() + return; + } + + let link = chatText.hoveredLink; + if(link.startsWith("#")){ + chatsModel.joinChat(link.substring(1), Constants.chatTypePublic); + return; + } + + if (link.startsWith('//')) { + let pk = link.replace("//", ""); + profileClick(chatsModel.userNameOrAlias(pk), pk, chatsModel.generateIdenticon(pk)) + return; + } + + Qt.openUrlExternally(link) + } +} + diff --git a/ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/NormalMessage.qml b/ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/NormalMessage.qml new file mode 100644 index 0000000000..9ef2b1a56b --- /dev/null +++ b/ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/NormalMessage.qml @@ -0,0 +1,152 @@ +import QtQuick 2.3 +import "../../../../../shared" +import "../../../../../imports" + +Item { + property var clickMessage: function () {} + property bool showImages: messageItem.appSettings.displayChatImages && imageUrls != "" + + id: chatTextItem + anchors.top: parent.top + anchors.topMargin: authorCurrentMsg != authorPrevMsg ? Style.current.smallPadding : 0 + height: childrenRect.height + this.anchors.topMargin + width: parent.width + + DateGroup { + id: dateGroupLbl + } + + UserImage { + id: chatImage + visible: (isMessage || isEmoji) && authorCurrentMsg != authorPrevMsg && !isCurrentUser + anchors.left: parent.left + anchors.leftMargin: Style.current.padding + anchors.top: dateGroupLbl.visible ? dateGroupLbl.bottom : parent.top + anchors.topMargin: 20 + } + + UsernameLabel { + id: chatName + visible: (isMessage || isEmoji) && authorCurrentMsg != authorPrevMsg && !isCurrentUser + text: userName + anchors.leftMargin: 20 + anchors.top: dateGroupLbl.visible ? dateGroupLbl.bottom : parent.top + anchors.topMargin: 0 + anchors.left: chatImage.right + } + + Rectangle { + property int chatVerticalPadding: 7 + property int chatHorizontalPadding: 12 + + id: chatBox + color: isSticker ? Style.current.background : (isCurrentUser ? Style.current.blue : Style.current.secondaryBackground) + border.color: isSticker ? Style.current.border : Style.current.transparent + border.width: 1 + height: (3 * chatVerticalPadding) + (contentType == Constants.stickerType ? stickerId.height : (chatText.height + chatReply.height)) + width: { + switch(contentType){ + case Constants.stickerType: + return stickerId.width + (2 * chatBox.chatHorizontalPadding); + default: + return plainText.length > 54 ? 400 : chatText.width + 2 * chatHorizontalPadding + } + } + + radius: 16 + 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: authorCurrentMsg != authorPrevMsg && !isCurrentUser ? chatImage.top : (dateGroupLbl.visible ? dateGroupLbl.bottom : parent.top) + anchors.topMargin: 0 + visible: isMessage || isEmoji + + ChatReply { + id: chatReply + anchors.top: parent.top + anchors.topMargin: chatReply.visible ? chatBox.chatVerticalPadding : 0 + anchors.left: parent.left + anchors.leftMargin: Style.current.padding + anchors.right: parent.right + anchors.rightMargin: chatBox.chatHorizontalPadding + color: isCurrentUser ? Style.current.blue : Style.current.lightBlue + + } + + ChatText { + id: chatText + anchors.top: chatReply.bottom + anchors.topMargin: chatBox.chatVerticalPadding + anchors.left: parent.left + anchors.leftMargin: parent.chatHorizontalPadding + anchors.right: plainText.length > 52 ? parent.right : undefined + anchors.rightMargin: plainText.length > 52 ? parent.chatHorizontalPadding : 0 + horizontalAlignment: !isCurrentUser ? Text.AlignLeft : Text.AlignRight + color: !isCurrentUser ? Style.current.textColor : Style.current.currentUserTextColor + } + + Sticker { + id: stickerId + anchors.left: parent.left + anchors.leftMargin: parent.chatHorizontalPadding + anchors.top: parent.top + anchors.topMargin: chatBox.chatVerticalPadding + } + + MessageMouseArea { + anchors.fill: parent + } + } + + ChatTime { + id: chatTime + anchors.top: showImages ? imageLoader.bottom : chatBox.bottom + anchors.topMargin: 4 + anchors.bottomMargin: Style.current.padding + anchors.right: showImages ? imageLoader.right : chatBox.right + anchors.rightMargin: isCurrentUser ? 5 : Style.current.padding + } + + SentMessage { + id: sentMessage + anchors.top: chatTime.top + anchors.bottomMargin: Style.current.padding + anchors.right: chatTime.left + anchors.rightMargin: 5 + } + + // This rectangle's only job is to mask the corner to make it less rounded... yep + Rectangle { + // TODO find a way to show the corner for stickers since they have a border + visible: isMessage || isEmoji + color: chatBox.color + width: 18 + height: 18 + anchors.bottom: chatBox.bottom + anchors.bottomMargin: 0 + anchors.left: !isCurrentUser ? chatBox.left : undefined + anchors.leftMargin: 0 + anchors.right: !isCurrentUser ? undefined : chatBox.right + anchors.rightMargin: 0 + radius: 4 + z: -1 + } + + Loader { + id: imageLoader + active: showImages + sourceComponent: imageComponent + 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: messageItem.appSettings.displayChatImages && imageUrls != "" ? chatBox.bottom : chatTime.bottom + anchors.topMargin: Style.current.smallPadding + } + + Component { + id: imageComponent + ImageMessage {} + } +} diff --git a/ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/SentMessage.qml b/ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/SentMessage.qml new file mode 100644 index 0000000000..13977a4e43 --- /dev/null +++ b/ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/SentMessage.qml @@ -0,0 +1,15 @@ +import QtQuick 2.3 +import "../../../../../shared" +import "../../../../../imports" + +StyledText { + id: sentMessage + visible: isCurrentUser && (isEmoji || isMessage || isSticker) + color: Style.current.darkGrey + text: outgoingStatus == "sent" ? + //% "Sent" + qsTrId("status-sent") : + //% "Sending..." + qsTrId("sending") + font.pixelSize: 10 +} diff --git a/ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/Sticker.qml b/ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/Sticker.qml new file mode 100644 index 0000000000..20a6f0ecd9 --- /dev/null +++ b/ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/Sticker.qml @@ -0,0 +1,10 @@ +import QtQuick 2.3 +import "../../../../../imports" + +Image { + id: stickerId + visible: contentType === Constants.stickerType + width: 140 + height: this.visible ? 140 : 0 + source: this.visible ? ("https://ipfs.infura.io/ipfs/" + sticker) : "" +} diff --git a/ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/UserImage.qml b/ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/UserImage.qml new file mode 100644 index 0000000000..d8243c29a0 --- /dev/null +++ b/ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/UserImage.qml @@ -0,0 +1,32 @@ +import QtQuick 2.3 +import "../../../../../shared" +import "../../../../../imports" + +Rectangle { + id: chatImage + visible: (isMessage || isEmoji) && authorCurrentMsg != authorPrevMsg + width: identiconImage.width + height: identiconImage.height + border.width: 1 + border.color: Style.current.border + radius: 50 + + Image { + id: identiconImage + width: 36 + height: chatImage.visible ? 36 : 0 + fillMode: Image.PreserveAspectFit + source: !isCurrentUser ? identicon : profileModel.profile.identicon + mipmap: true + smooth: false + antialiasing: true + + MouseArea { + cursorShape: Qt.PointingHandCursor + anchors.fill: parent + onClicked: { + clickMessage() + } + } + } +} diff --git a/ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/UsernameLabel.qml b/ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/UsernameLabel.qml new file mode 100644 index 0000000000..f6248f7682 --- /dev/null +++ b/ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/UsernameLabel.qml @@ -0,0 +1,22 @@ +import QtQuick 2.3 +import "../../../../../shared" +import "../../../../../imports" + +StyledTextEdit { + id: chatName + visible: (isMessage || isEmoji) && authorCurrentMsg != authorPrevMsg + height: this.visible ? 18 : 0 + text: !isCurrentUser ? userName : qsTr("You") + font.bold: true + font.pixelSize: 14 + readOnly: true + wrapMode: Text.WordWrap + selectByMouse: true + MouseArea { + cursorShape: Qt.PointingHandCursor + anchors.fill: parent + onClicked: { + clickMessage() + } + } +} diff --git a/ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/qmldir b/ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/qmldir new file mode 100644 index 0000000000..f17b4a3740 --- /dev/null +++ b/ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/qmldir @@ -0,0 +1 @@ +ChannelIdentifier 1.0 ChannelIdentifier.qml diff --git a/ui/app/AppLayouts/Chat/ChatColumn/qmldir b/ui/app/AppLayouts/Chat/ChatColumn/qmldir index 90449a4f3b..8fab2035fe 100644 --- a/ui/app/AppLayouts/Chat/ChatColumn/qmldir +++ b/ui/app/AppLayouts/Chat/ChatColumn/qmldir @@ -3,4 +3,6 @@ ChatMessages 1.0 ChatMessages.qml ChatInput 1.0 ChatInput.qml EmptyChat 1.0 EmptyChat.qml ChatButtons 1.0 ChatButtons.qml -ReplyArea 1.0 ReplyArea.qml \ No newline at end of file +ReplyArea 1.0 ReplyArea.qml +Message 1.0 Message.qml +CompactMessage 1.0 CompactMessage.qml diff --git a/ui/app/AppLayouts/Profile/Sections/HelpContainer.qml b/ui/app/AppLayouts/Profile/Sections/HelpContainer.qml index 473774394e..cbf157b784 100644 --- a/ui/app/AppLayouts/Profile/Sections/HelpContainer.qml +++ b/ui/app/AppLayouts/Profile/Sections/HelpContainer.qml @@ -12,19 +12,6 @@ Item { Layout.fillHeight: true Layout.fillWidth: true - function linkify(inputText) { - //URLs starting with http://, https://, or ftp:// - var replacePattern1 = /(\b(https?|ftp):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/gim; - var replacedText = inputText.replace(replacePattern1, "$1"); - - //URLs starting with "www." (without // before it, or it'd re-link the ones done above). - var replacePattern2 = /(^|[^\/])(www\.[\S]+(\b|$))/gim; - replacedText = replacedText.replace(replacePattern2, "$1$2"); - - replacedText = XSS.filterXSS(replacedText) - return replacedText; - } - StyledText { id: element8 //% "Help menus: FAQ, Glossary, etc." @@ -39,7 +26,7 @@ Item { StyledText { anchors.centerIn: parent - text: linkify(link) + text: Utils.linkifyAndXSS(link) onLinkActivated: Qt.openUrlExternally(link) MouseArea { diff --git a/ui/imports/Utils.qml b/ui/imports/Utils.qml index 061b801c88..057e65d2b3 100644 --- a/ui/imports/Utils.qml +++ b/ui/imports/Utils.qml @@ -1,6 +1,7 @@ pragma Singleton import QtQuick 2.13 +import "../shared/xss.js" as XSS QtObject { function isHex(value) { @@ -39,4 +40,17 @@ QtObject { } return addr.substring(0, 2 + numberOfChars) + "..." + addr.substring(addr.length - numberOfChars); } + + function linkifyAndXSS(inputText) { + //URLs starting with http://, https://, or ftp:// + var replacePattern1 = /(\b(https?|ftp):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/gim; + var replacedText = inputText.replace(replacePattern1, "$1"); + + //URLs starting with "www." (without // before it, or it'd re-link the ones done above). + var replacePattern2 = /(^|[^\/])(www\.[\S]+(\b|$))/gim; + replacedText = replacedText.replace(replacePattern2, "$1$2"); + + replacedText = XSS.filterXSS(replacedText) + return replacedText; + } } diff --git a/ui/nim-status-client.pro b/ui/nim-status-client.pro index 7084271fb0..8a3fa0b9b7 100644 --- a/ui/nim-status-client.pro +++ b/ui/nim-status-client.pro @@ -55,6 +55,22 @@ else: unix:!android: target.path = /opt/$${TARGET}/bin !isEmpty(target.path): INSTALLS += target DISTFILES += \ + app/AppLayouts/Chat/ChatColumn/CompactMessage.qml \ + app/AppLayouts/Chat/ChatColumn/MessageComponents/ChannelIdentifier.qml \ + app/AppLayouts/Chat/ChatColumn/MessageComponents/ChatReply \ + app/AppLayouts/Chat/ChatColumn/MessageComponents/ChatReply.qml \ + app/AppLayouts/Chat/ChatColumn/MessageComponents/ChatText.qml \ + app/AppLayouts/Chat/ChatColumn/MessageComponents/ChatTime.qml \ + app/AppLayouts/Chat/ChatColumn/MessageComponents/CompactMessage.qml \ + app/AppLayouts/Chat/ChatColumn/MessageComponents/DateGroup.qml \ + app/AppLayouts/Chat/ChatColumn/MessageComponents/ImageMessage.qml \ + app/AppLayouts/Chat/ChatColumn/MessageComponents/MessageMouseArea.qml \ + app/AppLayouts/Chat/ChatColumn/MessageComponents/NormalMessage.qml \ + app/AppLayouts/Chat/ChatColumn/MessageComponents/SentMessage.qml \ + app/AppLayouts/Chat/ChatColumn/MessageComponents/Sticker.qml \ + app/AppLayouts/Chat/ChatColumn/MessageComponents/UserImage.qml \ + app/AppLayouts/Chat/ChatColumn/MessageComponents/UsernameLabel.qml \ + app/AppLayouts/Chat/ChatColumn/MessageComponents/qmldir \ app/AppLayouts/Chat/ContactsColumn/ClosedEmptyView.qml \ app/AppLayouts/Chat/components/EmojiPopup.qml \ app/AppLayouts/Chat/components/InviteFriendsPopup.qml \