import QtQuick 2.12 import QtQuick.Controls 2.12 import QtQuick.Layouts 1.12 import QtQuick.Controls.Material 2.12 import Spectral 0.1 import Spectral.Setting 0.1 import Spectral.Component 2.0 import Spectral.Dialog 2.0 import Spectral.Menu.Timeline 2.0 import Spectral.Effect 2.0 ColumnLayout { readonly property bool avatarVisible: !sentByMe && showAuthor readonly property bool sentByMe: author.isLocalUser readonly property bool darkBackground: !sentByMe readonly property bool replyVisible: reply || false readonly property bool failed: marks === EventStatus.SendingFailed readonly property color authorColor: eventType === "notice" ? MPalette.primary : author.color readonly property color replyAuthorColor: replyVisible ? reply.author.color : MPalette.accent signal saveFileAs() signal openExternally() id: root z: -5 spacing: 0 RowLayout { Layout.alignment: sentByMe ? Qt.AlignRight : Qt.AlignLeft id: messageRow spacing: 4 Avatar { Layout.preferredWidth: 36 Layout.preferredHeight: 36 Layout.alignment: Qt.AlignBottom visible: avatarVisible hint: author.displayName source: author.avatarMediaId color: author.color Component { id: userDetailDialog UserDetailDialog {} } RippleEffect { anchors.fill: parent circular: true onClicked: userDetailDialog.createObject(ApplicationWindow.overlay, {"room": currentRoom, "user": author.object, "displayName": author.displayName, "avatarMediaId": author.avatarMediaId, "avatarUrl": author.avatarUrl}).open() } } Item { Layout.preferredWidth: 36 Layout.preferredHeight: 36 visible: !(sentByMe || avatarVisible) } Control { Layout.maximumWidth: messageListView.width - (!sentByMe ? 36 + messageRow.spacing : 0) - 48 Layout.minimumHeight: 36 padding: 0 background: AutoRectangle { readonly property int minorRadius: 8 id: bubbleBackground color: sentByMe ? MPalette.background : authorColor radius: 18 topLeftVisible: !sentByMe && (bubbleShape == 3 || bubbleShape == 2) topRightVisible: sentByMe && (bubbleShape == 3 || bubbleShape == 2) bottomLeftVisible: !sentByMe && (bubbleShape == 1 || bubbleShape == 2) bottomRightVisible: sentByMe && (bubbleShape == 1 || bubbleShape == 2) topLeftRadius: minorRadius topRightRadius: minorRadius bottomLeftRadius: minorRadius bottomRightRadius: minorRadius AutoMouseArea { anchors.fill: parent id: messageMouseArea onSecondaryClicked: { var contextMenu = messageDelegateContextMenu.createObject(root) contextMenu.viewSource.connect(function() { messageSourceDialog.createObject(ApplicationWindow.overlay, {"sourceText": toolTip}).open() }) contextMenu.reply.connect(function() { roomPanelInput.replyModel = Object.assign({}, model) roomPanelInput.isReply = true roomPanelInput.focus() }) contextMenu.redact.connect(function() { currentRoom.redactEvent(eventId) }) contextMenu.popup() } Component { id: messageDelegateContextMenu MessageDelegateContextMenu {} } Component { id: messageSourceDialog MessageSourceDialog {} } } } contentItem: ColumnLayout { spacing: 0 Control { Layout.fillWidth: true Layout.topMargin: 8 Layout.leftMargin: 8 Layout.rightMargin: 8 id: replyControl padding: 4 rightPadding: 12 visible: replyVisible contentItem: RowLayout { Avatar { Layout.preferredWidth: 28 Layout.preferredHeight: 28 Layout.alignment: Qt.AlignTop source: replyVisible ? reply.author.avatarMediaId : "" hint: replyVisible ? reply.author.displayName : "H" color: replyVisible ? reply.author.color : MPalette.accent RippleEffect { anchors.fill: parent circular: true onClicked: userDetailDialog.createObject(ApplicationWindow.overlay, {"room": currentRoom, "user": reply.author.object, "displayName": reply.author.displayName, "avatarMediaId": reply.author.avatarMediaId, "avatarUrl": reply.author.avatarUrl}).open() } } TextEdit { Layout.fillWidth: true color: !sentByMe ? MPalette.foreground : "white" text: "" + (replyVisible ? reply.display : "") font.family: window.font.family selectByMouse: true readOnly: true wrapMode: Label.Wrap selectedTextColor: darkBackground ? "white" : replyAuthorColor selectionColor: darkBackground ? replyAuthorColor : "white" textFormat: Text.RichText } } background: Rectangle { color: sentByMe ? replyAuthorColor : MPalette.background radius: 18 AutoMouseArea { anchors.fill: parent onClicked: goToEvent(reply.eventId) } } } TextEdit { Layout.fillWidth: true Layout.leftMargin: 16 Layout.rightMargin: 16 Layout.topMargin: 8 Layout.bottomMargin: 8 id: contentLabel text: "" + display color: darkBackground ? "white" : MPalette.foreground font.family: window.font.family font.pixelSize: (message.length === 2 && /(?:[\u2700-\u27bf]|(?:\ud83c[\udde6-\uddff]){2}|[\ud800-\udbff][\udc00-\udfff]|[\u0023-\u0039]\ufe0f?\u20e3|\u3299|\u3297|\u303d|\u3030|\u24c2|\ud83c[\udd70-\udd71]|\ud83c[\udd7e-\udd7f]|\ud83c\udd8e|\ud83c[\udd91-\udd9a]|\ud83c[\udde6-\uddff]|\ud83c[\ude01-\ude02]|\ud83c\ude1a|\ud83c\ude2f|\ud83c[\ude32-\ude3a]|\ud83c[\ude50-\ude51]|\u203c|\u2049|[\u25aa-\u25ab]|\u25b6|\u25c0|[\u25fb-\u25fe]|\u00a9|\u00ae|\u2122|\u2139|\ud83c\udc04|[\u2600-\u26FF]|\u2b05|\u2b06|\u2b07|\u2b1b|\u2b1c|\u2b50|\u2b55|\u231a|\u231b|\u2328|\u23cf|[\u23e9-\u23f3]|[\u23f8-\u23fa]|\ud83c\udccf|\u2934|\u2935|[\u2190-\u21ff])/g.test(message)) ? 48 : 14 selectByMouse: true readOnly: true wrapMode: Label.Wrap selectedTextColor: darkBackground ? authorColor : "white" selectionColor: darkBackground ? "white" : authorColor textFormat: Text.RichText onLinkActivated: { if (link.startsWith("https://matrix.to/")) { var result = link.replace(/\?.*/, "").match("https://matrix.to/#/(!.*:.*)/(\\$.*:.*)") if (!result || result.length < 3) return if (result[1] != currentRoom.id) return if (!result[2]) return goToEvent(result[2]) } else { Qt.openUrlExternally(link) } } MouseArea { anchors.fill: parent acceptedButtons: Qt.NoButton cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : Qt.IBeamCursor } } ReactionDelegate { Layout.fillWidth: true Layout.topMargin: 0 Layout.bottomMargin: 8 Layout.leftMargin: 16 Layout.rightMargin: 16 } } } } RowLayout { Layout.alignment: sentByMe ? Qt.AlignRight : Qt.AlignLeft Layout.leftMargin: sentByMe ? undefined : 36 + messageRow.spacing + 12 Layout.rightMargin: sentByMe ? 12 : undefined Layout.bottomMargin: 4 visible: showAuthor && !failed Label { visible: !sentByMe text: author.displayName color: MPalette.lighter } Label { text: Qt.formatTime(time, "hh:mm AP") color: MPalette.lighter } } RowLayout { Layout.alignment: sentByMe ? Qt.AlignRight : Qt.AlignLeft Layout.leftMargin: sentByMe ? undefined : 36 + messageRow.spacing + 12 Layout.rightMargin: sentByMe ? 12 : undefined Layout.bottomMargin: 4 visible: failed Label { text: "Send failed:" color: MPalette.lighter } Label { text: "Resend" color: MPalette.lighter MouseArea { anchors.fill: parent onClicked: currentRoom.retryMessage(eventId) } } Label { text: "|" color: MPalette.lighter } Label { text: "Discard" color: MPalette.lighter MouseArea { anchors.fill: parent onClicked: currentRoom.discardMessage(eventId) } } } }