feat: refactor Message and add Compact message type

This commit is contained in:
Jonathan Rainville 2020-07-15 17:04:14 -04:00 committed by Iuri Matias
parent a0c5f8624c
commit 5951fcf131
20 changed files with 793 additions and 527 deletions

View File

@ -122,6 +122,10 @@ ScrollView {
} }
model: messageList model: messageList
ProfilePopup {
id: profilePopup
}
delegate: Message { delegate: Message {
id: msgDelegate id: msgDelegate
fromAuthor: model.fromAuthor fromAuthor: model.fromAuthor
@ -152,7 +156,6 @@ ScrollView {
scrollToBottom: scrollView.scrollToBottom scrollToBottom: scrollView.scrollToBottom
} }
} }
} }
/*##^## /*##^##

View File

@ -1,11 +1,7 @@
import QtQuick 2.3 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"
import "../../../../shared/xss.js" as XSS
import "../../../../imports" import "../../../../imports"
import "./MessageComponents"
import "../components" import "../components"
Item { Item {
@ -39,534 +35,78 @@ Item {
property var profileClick: function () {} property var profileClick: function () {}
property var scrollToBottom: function () {} property var scrollToBottom: function () {}
property var appSettings property var appSettings
property bool isCompact: true
id: messageItem
width: parent.width width: parent.width
anchors.right: !isCurrentUser ? undefined : parent.right anchors.right: !isCurrentUser ? undefined : parent.right
id: messageWrapper
height: { height: {
switch(contentType){ switch(contentType) {
case Constants.chatIdentifier: case Constants.chatIdentifier:
return channelIdentifier.height + channelIdentifier.verticalMargin return childrenRect.height + 50
case Constants.stickerType: default: return childrenRect.height
return stickerId.height + 50 + (dateGroupLbl.visible ? 50 : 0)
default:
return childrenRect.height
} }
} }
function linkify(inputText) { function clickMessage() {
// URLs starting with http://, https://, or ftp:// SelectedMessage.set(messageId, fromAuthor);
var replacePattern1 = /(\b(https?|ftp):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/gim; profileClick(userName, fromAuthor, identicon);
var replacedText = inputText.replace(replacePattern1, "<a href='$1'>$1</a>"); messageContextMenu.popup()
// 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<a href='http://$2'>$2</a>");
replacedText = XSS.filterXSS(replacedText)
return replacedText;
} }
ProfilePopup { Loader {
id: profilePopup 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 { Component {
property int verticalMargin: 50 id: channelIdentifierComponent
id: channelIdentifier ChannelIdentifier {
visible: authorCurrentMsg == "" authorCurrentMsg: messageItem.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()
}
}
}
} }
} }
// Private group Messages // Private group Messages
StyledText { Component {
wrapMode: Text.Wrap id: privateGroupHeaderComponent
text: message StyledText {
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 `<html>
<head>
<style type="text/css">
code {
background-color: #1a356b;
color: #FFFFFF;
white-space: pre;
}
p {
white-space: pre;
}
a.mention {
color: ${isCurrentUser ? Style.current.black : Style.current.white}
font-weight: bold;
}
blockquote {
margin: 0;
padding: 0;
}
</style>
</head>
<body>
${Emoji.parse(msg, "26x26")}
</body>
</html>`;
}
}
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
wrapMode: Text.Wrap wrapMode: Text.Wrap
anchors.top: chatReply.bottom text: message
anchors.topMargin: chatBox.chatVerticalPadding visible: isStatusMessage
font.pixelSize: 15 font.pixelSize: 16
readOnly: true color: Style.current.darkGrey
selectByMouse: true width: parent.width - 120
color: !isCurrentUser ? Style.current.textColor : Style.current.currentUserTextColor horizontalAlignment: Text.AlignHCenter
visible: contentType == Constants.messageType || isEmoji anchors.horizontalCenter: parent.horizontalCenter
} textFormat: Text.RichText
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)
}
} }
} }
StyledTextEdit { // Normal message
id: chatTime Component {
color: Style.current.darkGrey id: messageComponent
text: { NormalMessage {
let messageDate = new Date(Math.floor(timestamp)) clickMessage: messageItem.clickMessage
let minutes = messageDate.getMinutes();
let hours = messageDate.getHours();
return (hours < 10 ? "0" + hours : hours) + ":" + (minutes < 10 ? "0" + minutes : minutes)
} }
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 { // Compact Messages
property int chatVerticalPadding: 12 Component {
property int chatHorizontalPadding: 12 id: compactMessageComponent
property int imageWidth: 350 CompactMessage {
clickMessage: messageItem.clickMessage
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
} }
} }
} }

View File

@ -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}
}
##^##*/

View File

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

View File

@ -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 `<html>
<head>
<style type="text/css">
code {
background-color: #1a356b;
color: #FFFFFF;
white-space: pre;
}
p {
white-space: pre;
}
a.mention {
color: ${isCurrentUser ? Style.current.black : Style.current.white}
font-weight: bold;
}
blockquote {
margin: 0;
padding: 0;
}
</style>
</head>
<body>
${Emoji.parse(msg, "26x26")}
</body>
</html>`;
}
}
horizontalAlignment: Text.AlignLeft
wrapMode: Text.Wrap
font.pixelSize: 15
readOnly: true
selectByMouse: true
color: Style.current.textColor
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1 @@
ChannelIdentifier 1.0 ChannelIdentifier.qml

View File

@ -3,4 +3,6 @@ ChatMessages 1.0 ChatMessages.qml
ChatInput 1.0 ChatInput.qml ChatInput 1.0 ChatInput.qml
EmptyChat 1.0 EmptyChat.qml EmptyChat 1.0 EmptyChat.qml
ChatButtons 1.0 ChatButtons.qml ChatButtons 1.0 ChatButtons.qml
ReplyArea 1.0 ReplyArea.qml ReplyArea 1.0 ReplyArea.qml
Message 1.0 Message.qml
CompactMessage 1.0 CompactMessage.qml

View File

@ -12,19 +12,6 @@ Item {
Layout.fillHeight: true Layout.fillHeight: true
Layout.fillWidth: 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, "<a href='$1'>$1</a>");
//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<a href='http://$2'>$2</a>");
replacedText = XSS.filterXSS(replacedText)
return replacedText;
}
StyledText { StyledText {
id: element8 id: element8
//% "Help menus: FAQ, Glossary, etc." //% "Help menus: FAQ, Glossary, etc."
@ -39,7 +26,7 @@ Item {
StyledText { StyledText {
anchors.centerIn: parent anchors.centerIn: parent
text: linkify(link) text: Utils.linkifyAndXSS(link)
onLinkActivated: Qt.openUrlExternally(link) onLinkActivated: Qt.openUrlExternally(link)
MouseArea { MouseArea {

View File

@ -1,6 +1,7 @@
pragma Singleton pragma Singleton
import QtQuick 2.13 import QtQuick 2.13
import "../shared/xss.js" as XSS
QtObject { QtObject {
function isHex(value) { function isHex(value) {
@ -39,4 +40,17 @@ QtObject {
} }
return addr.substring(0, 2 + numberOfChars) + "..." + addr.substring(addr.length - numberOfChars); 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, "<a href='$1'>$1</a>");
//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<a href='http://$2'>$2</a>");
replacedText = XSS.filterXSS(replacedText)
return replacedText;
}
} }

View File

@ -55,6 +55,22 @@ else: unix:!android: target.path = /opt/$${TARGET}/bin
!isEmpty(target.path): INSTALLS += target !isEmpty(target.path): INSTALLS += target
DISTFILES += \ 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/ContactsColumn/ClosedEmptyView.qml \
app/AppLayouts/Chat/components/EmojiPopup.qml \ app/AppLayouts/Chat/components/EmojiPopup.qml \
app/AppLayouts/Chat/components/InviteFriendsPopup.qml \ app/AppLayouts/Chat/components/InviteFriendsPopup.qml \