diff --git a/src/apps/qml/qml/messages/AttachmentMessageAudio.qml b/src/apps/qml/qml/messages/AttachmentMessageAudio.qml index a63e82c0..4affb7b2 100644 --- a/src/apps/qml/qml/messages/AttachmentMessageAudio.qml +++ b/src/apps/qml/qml/messages/AttachmentMessageAudio.qml @@ -1,162 +1,165 @@ /* Copyright (c) 2017-2020 Laurent Montel This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License or ( at your option ) version 3 or, at the discretion of KDE e.V. ( which shall act as a proxy as in section 14 of the GPLv3 ), any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import QtQuick 2.9 import QtMultimedia 5.8 import QtQuick.Controls 2.5 as QQC2 import org.kde.kirigami 2.7 as Kirigami import QtQuick.Layouts 1.12 import Ruqola 1.0 import "../js/convert.js" as ConvertScript; import "../common" MessageBase { id: attachmentAudio RowLayout { AvatarImage { id: avatarRect avatarurl: i_avatar aliasname: i_aliasname username: i_username onShowUserInfo: { //TODO } } Repeater { id: repearterAttachments model: i_attachments MediaPlayer { id: audioPlayer autoPlay: false onPaused: { playerButton.source = "media-playback-start" } onPlaying: { playerButton.source = "media-playback-pause" } onStopped: { playerButton.source = "media-playback-start" playerSlider.value=0 } onPositionChanged: { playerSlider.sync = true playerSlider.value = audioPlayer.position / audioPlayer.duration playerSlider.sync = false timeLabel.text = ConvertScript.convertTimeString(audioPlayer.position) + "/" + ConvertScript.convertTimeString(audioPlayer.duration) } source: rcAccount.attachmentUrl(model.modelData.link) } ColumnLayout { Layout.fillWidth: true QQC2.Label { //TODO remove duplicate code text: model.modelData.title === "" ? "" : i18n("File Uploaded: %1", model.modelData.title) textFormat: Text.PlainText visible: model.modelData.title !== "" wrapMode: QQC2.Label.Wrap anchors.leftMargin: Kirigami.Units.smallSpacing anchors.rightMargin: Kirigami.Units.smallSpacing } RowLayout { Kirigami.Icon { id: playerButton source: "media-playback-start" width: height height: Kirigami.Units.iconSizes.huge MouseArea { anchors.fill: parent onClicked: { console.log(RuqolaDebugCategorySingleton.category, "Click on download audio file"); if (repearterAttachments.audioPlayer.source !== "") { if (repearterAttachments.audioPlayer.playbackState === MediaPlayer.PlayingState) { repearterAttachments.audioPlayer.pause() playerButton.source = "media-playback-start" } else { repearterAttachments.audioPlayer.play() playerButton.source = "media-playback-pause" } } else { console.log(RuqolaDebugCategorySingleton.category, "Audio file no found"); } } } } QQC2.Slider { id: playerSlider Layout.fillWidth: true property bool sync: false onValueChanged: { if (!sync) { audioPlayer.seek(value * audioPlayer.duration) } } } QQC2.Label { id: timeLabel //TODO display real value text: "00:00/00:00" } DownloadButton { id: download onDownloadButtonClicked: { messageMain.downloadAttachment(model.modelData.link) } } } QQC2.Label { text: model.modelData.description visible: model.modelData.description !== "" wrapMode: QQC2.Label.Wrap anchors.leftMargin: Kirigami.Units.smallSpacing anchors.rightMargin: Kirigami.Units.smallSpacing } } } RowLayout { RepeaterReactions { id: repearterReactions model: i_reactions + onAddReaction: { + messageMain.addReaction(i_messageID, emoji) + } onDeleteReaction: { attachmentAudio.deleteReaction(i_messageID, emoji) } } } TimestampText { id: timestampText timestamp: i_timestamp } } } diff --git a/src/apps/qml/qml/messages/AttachmentMessageImage.qml b/src/apps/qml/qml/messages/AttachmentMessageImage.qml index 36ac43ad..21b7a871 100644 --- a/src/apps/qml/qml/messages/AttachmentMessageImage.qml +++ b/src/apps/qml/qml/messages/AttachmentMessageImage.qml @@ -1,252 +1,255 @@ /* Copyright (c) 2017-2020 Laurent Montel This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License or ( at your option ) version 3 or, at the discretion of KDE e.V. ( which shall act as a proxy as in section 14 of the GPLv3 ), any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import QtQuick 2.9 import QtQuick.Controls 2.5 as QQC2 import org.kde.kirigami 2.7 as Kirigami import Ruqola 1.0 import QtQuick.Layouts 1.12 import "../common" MessageBase { id: messageMain Loader { id: messageMenuLoader active: false property var posX property var posY sourceComponent :MessageMenu { id: menu x: messageMenuLoader.posX y: messageMenuLoader.posY can_edit_message: i_can_edit_message starred: i_starred Component.onCompleted: { open() } onAboutToHide: { messageMenuLoader.active = false } } } RowLayout { AvatarImage { id: avatarRect avatarurl: i_avatar aliasname: i_aliasname username: i_username onShowUserInfo: { //TODO } } ColumnLayout { QQC2.Label { id: usernameLabel Layout.alignment: Qt.AlignLeft font.bold: true text: i_aliasname + ' ' + i_usernameurl + (i_editedByUserName === "" ? "" : " " + i18n("(edited by %1)", i_editedByUserName)) height: avatarRect.height onLinkActivated: messageMain.linkActivated(link) MouseArea { anchors.fill: parent acceptedButtons: Qt.RightButton onClicked: { if (i_useMenuMessage) { if (mouse.button === Qt.RightButton) { messageMenuLoader.posX = mouse.x messageMenuLoader.posY = mouse.y if (messageMenuLoader.active) messageMenuLoader.active = false else messageMenuLoader.active = true } } } } visible: !i_groupable } Repeater { id: repearterAttachments model: i_attachments Row { spacing: Kirigami.Units.smallSpacing Column { QQC2.Label { id: imageTitle text: model.modelData.title === "" ? "" : model.modelData.imageTitle visible: model.modelData.title !== "" wrapMode: QQC2.Label.NoWrap anchors.leftMargin: Kirigami.Units.smallSpacing anchors.rightMargin: Kirigami.Units.smallSpacing textFormat: Text.RichText onLinkActivated: { messageMain.displayImage(imageUrl.source, model.modelData.title, model.modelData.isAnimatedImage) } MouseArea { anchors.fill: parent acceptedButtons: Qt.RightButton onClicked: { if (mouse.button === Qt.RightButton) { messageMenuLoader.posX = mouse.x messageMenuLoader.posY = mouse.y if (messageMenuLoader.active) messageMenuLoader.active = false else messageMenuLoader.active = true } } } } Image { id: imageUrl visible: model.modelData.isAnimatedImage readonly property int imageHeight: model.modelData.imageHeight === -1 ? 200 : Math.min(200, model.modelData.imageHeight) source: rcAccount.attachmentUrl(model.modelData.link) asynchronous: true fillMode: Image.PreserveAspectFit //Don't use really imageWidth otherwise it will be too big width: 200 //model.modelData.imageWidth === -1 ? 200 : model.modelData.imageWidth height: 0 sourceSize.width: 200 sourceSize.height: 200 onStatusChanged: { if(status == Image.Error){ console.log(RuqolaDebugCategorySingleton.category, "Image load error! Trying to reload. " + source) } } MouseArea { anchors.fill: parent onClicked: { if(status === Image.Error) { console.log(RuqolaDebugCategorySingleton.category, "Image not loaded."); } else { messageMain.displayImage(imageUrl.source, imageTitle.text, model.modelData.isAnimatedImage) } } } } AnimatedImage { id: imageAnimatedUrl visible: model.modelData.isAnimatedImage readonly property int imageHeight: model.modelData.imageHeight === -1 ? 200 : Math.min(200, model.modelData.imageHeight) source: rcAccount.attachmentUrl(model.modelData.link) asynchronous: true fillMode: Image.PreserveAspectFit //Don't use really imageWidth otherwise it will be too big width: 200 //model.modelData.imageWidth === -1 ? 200 : model.modelData.imageWidth height: 0 onStatusChanged: { if(status == Image.Error){ console.log(RuqolaDebugCategorySingleton.category, "Image load error! Trying to reload. " + source) } } onHeightChanged: { playing = height > 0; } MouseArea { anchors.fill: parent onClicked: { if(status === Image.Error) { console.log(RuqolaDebugCategorySingleton.category, "Image not loaded."); } else { messageMain.displayImage(imageAnimatedUrl.source, imageTitle.text, model.modelData.isAnimatedImage) } } } } QQC2.Label { text: model.modelData.description wrapMode: QQC2.Label.Wrap anchors.leftMargin: Kirigami.Units.smallSpacing anchors.rightMargin: Kirigami.Units.smallSpacing visible: model.modelData.description !== "" MouseArea { anchors.fill: parent acceptedButtons: Qt.RightButton onClicked: { if (mouse.button === Qt.RightButton) { menu.x = mouse.x menu.y = mouse.y menu.open(); } } } } } ShowHideButton { targetAnimation: model.modelData.isAnimatedImage ? imageAnimatedUrl : imageUrl defaultHeight: model.modelData.isAnimatedImage ? imageAnimatedUrl.imageHeight : Url.imageHeight onHiddenChanged: { messageMain.showDisplayAttachment(i_messageID, state) } } DownloadButton { id: download onDownloadButtonClicked: { messageMain.downloadAttachment(model.modelData.link) } } Connections { target: rcAccount onFileDownloaded: { //console.log(RuqolaDebugCategorySingleton.category, " IMAGE SUPPORT: " + filePath + " cacheImageUrl :" + cacheImageUrl + " model.modelData.link: " + model.modelData.link) if (filePath === model.modelData.link) { console.log(RuqolaDebugCategorySingleton.category, "Image updated: " + cacheImageUrl) imageUrl.source = cacheImageUrl; imageAnimatedUrl.source = cacheImageUrl; } } } } } RowLayout { RepeaterReactions { id: repearterReactions model: i_reactions + onAddReaction: { + messageMain.addReaction(i_messageID, emoji) + } onDeleteReaction: { messageMain.deleteReaction(i_messageID, emoji) } } } } Item { Layout.fillWidth: true } TimestampText { id: timestampText timestamp: i_timestamp } } } diff --git a/src/apps/qml/qml/messages/AttachmentMessageVideo.qml b/src/apps/qml/qml/messages/AttachmentMessageVideo.qml index ed746583..759c47f3 100644 --- a/src/apps/qml/qml/messages/AttachmentMessageVideo.qml +++ b/src/apps/qml/qml/messages/AttachmentMessageVideo.qml @@ -1,178 +1,181 @@ /* Copyright (c) 2017-2020 Laurent Montel This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License or ( at your option ) version 3 or, at the discretion of KDE e.V. ( which shall act as a proxy as in section 14 of the GPLv3 ), any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import QtQuick 2.9 import QtQuick.Controls 2.5 as QQC2 import org.kde.kirigami 2.7 as Kirigami import QtMultimedia 5.8 import QtQuick.Layouts 1.12 import Ruqola 1.0 import "../js/convert.js" as ConvertScript; import "../common" MessageBase { id: attachmentVideo MediaPlayer { id: videoPlayer autoPlay: false //It doesn't work. Perhaps we need to remove it function preview() { if (status === MediaPlayer.Loaded && playbackState === MediaPlayer.StoppedState) { seek(duration/2) } } onStatusChanged: { preview(); } onPlaybackStateChanged: { preview(); } } RowLayout { AvatarImage { id: avatarRect avatarurl: i_avatar aliasname: i_aliasname username: i_username onShowUserInfo: { //TODO } } Repeater { id: repearterAttachments model: i_attachments RowLayout { ColumnLayout { Layout.fillWidth: true QQC2.Label { //TODO remove duplicate code text: model.modelData.title === "" ? "" : i18n("File Uploaded: %1", model.modelData.title) visible: model.modelData.title !== "" wrapMode: QQC2.Label.Wrap anchors.leftMargin: Kirigami.Units.smallSpacing anchors.rightMargin: Kirigami.Units.smallSpacing } VideoOutput { id: videoOutput property int videoHeight: 100 Layout.fillWidth: true source: videoPlayer width: 100 height: 0 } RowLayout { //Add video media Kirigami.Icon { id: playerButton source: "media-playback-start" width: height height: Kirigami.Units.iconSizes.medium MouseArea { anchors.fill: parent readonly property url link: rcAccount.attachmentUrl(model.modelData.link) onLinkChanged: { videoPlayer.source = link } onClicked: { console.log(RuqolaDebugCategorySingleton.category, "Click on video file!"); if (videoPlayer.playbackState === MediaPlayer.PlayingState) { videoPlayer.pause() playerButton.source = "media-playback-start" } else { videoPlayer.play() playerButton.source = "media-playback-pause" } //TODO stop ? if (videoPlayer.error !== MediaPlayer.NoError) { console.log(RuqolaDebugCategorySingleton.category, "Video file no found"); } } } } QQC2.Slider { id: playerSlider enabled: videoPlayer.playbackState === MediaPlayer.PlayingState Layout.fillWidth: true from: 0 to: videoPlayer.duration value: videoPlayer.position onMoved: { videoPlayer.seek(value) } } QQC2.Label { id: timeLabel text: ConvertScript.convertTimeString(playerSlider.value) + "/" + ConvertScript.convertTimeString(playerSlider.to)//"00:00/00:00" } DownloadButton { id: download onDownloadButtonClicked: { messageMain.downloadAttachment(videoPlayer.source) } } ShowHideButton { targetAnimation: videoOutput defaultHeight: videoOutput.videoHeight } } QQC2.Label { text: model.modelData.description visible: model.modelData.description !== "" wrapMode: QQC2.Label.Wrap anchors.leftMargin: Kirigami.Units.smallSpacing anchors.rightMargin: Kirigami.Units.smallSpacing } } } } RowLayout { RepeaterReactions { id: repearterReactions model: i_reactions + onAddReaction: { + messageMain.addReaction(i_messageID, emoji) + } onDeleteReaction: { attachmentVideo.deleteReaction(i_messageID, emoji) } } } TimestampText { id: timestampText timestamp: i_timestamp } } } diff --git a/src/apps/qml/qml/messages/RepeaterReactions.qml b/src/apps/qml/qml/messages/RepeaterReactions.qml index 914acb92..dfb6dc5c 100644 --- a/src/apps/qml/qml/messages/RepeaterReactions.qml +++ b/src/apps/qml/qml/messages/RepeaterReactions.qml @@ -1,86 +1,97 @@ /* * Copyright (C) 2018-2020 Laurent Montel * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ import QtQuick 2.9 import org.kde.kirigami 2.7 as Kirigami import QtQuick.Controls 2.5 as QQC2 import QtQuick.Layouts 1.12 Repeater { id: repearterReactions + + signal addReaction(string emoji) signal deleteReaction(string emoji) + Rectangle { + id: item + + readonly property bool reactionAdded: model.modelData.userNames.indexOf(appid.rocketChatAccount.userName) !== -1 + radius: 5 width: row.width + 2 * Kirigami.Units.smallSpacing height: row.height + 2 * Kirigami.Units.smallSpacing border.color: Kirigami.Theme.linkBackgroundColor + border.width: reactionAdded ? 2 : 1 RowLayout { id: row anchors { centerIn: parent margins: Kirigami.Units.smallSpacing } spacing: Kirigami.Units.smallSpacing AnimatedImage { id: imageAnimated visible: model.modelData.isAnimatedImage source: model.modelData.isAnimatedImage ? model.modelData.convertedReactionName : "" //Verify it Layout.preferredWidth: 20 Layout.preferredHeight: height asynchronous: true } QQC2.Label { id: reactionsType visible: !model.modelData.isAnimatedImage textFormat: Text.RichText text: model.modelData.convertedReactionName wrapMode: QQC2.Label.NoWrap font.pixelSize: 8 } QQC2.Label { id: count Layout.fillHeight: true text: model.modelData.count visible: model.modelData.count > 0 wrapMode: QQC2.Label.NoWrap - font.italic: true font.pixelSize: 9 } } MouseArea { id: mouseArea anchors.fill: parent acceptedButtons: Qt.RightButton | Qt.LeftButton cursorShape: containsMouse ? Qt.PointingHandCursor : Qt.ArrowCursor hoverEnabled: true onClicked: { - repearterReactions.deleteReaction(model.modelData.reactionName); + if (item.reactionAdded) { + repearterReactions.deleteReaction(model.modelData.reactionName); + } else { + repearterReactions.addReaction(model.modelData.reactionName); + } } } QQC2.ToolTip.visible: mouseArea.containsMouse QQC2.ToolTip.text: model.modelData.convertedUsersNameAtToolTip } } diff --git a/src/apps/qml/qml/messages/UserMessage.qml b/src/apps/qml/qml/messages/UserMessage.qml index b5b0a485..b27ce777 100644 --- a/src/apps/qml/qml/messages/UserMessage.qml +++ b/src/apps/qml/qml/messages/UserMessage.qml @@ -1,291 +1,294 @@ /* * Copyright 2016 Riccardo Iaconelli * Copyright (c) 2017-2020 Laurent Montel * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ import QtQuick 2.9 import org.kde.kirigami 2.7 as Kirigami import QtQuick.Controls 2.5 as QQC2 import QtQuick.Layouts 1.12 import Ruqola 1.0 import "../common" MessageBase { property string i_messageID property var i_urls property var i_attachments property string i_own_username id: messageMain Layout.alignment: Qt.AlignTop Loader { id: messageMenuLoader active: false property var posX property var posY sourceComponent: MessageMenu { id: menu x: messageMenuLoader.posX y: messageMenuLoader.posY can_edit_message: i_can_edit_message user_ignored : i_user_ignored starred: i_starred pinned_message: i_pinned showTranslatedMessage: i_showTranslatedMessage Component.onCompleted: { open() } onAboutToHide: { messageMenuLoader.active = false; } } } RowLayout { AvatarImage { id: avatarRect avatarurl: i_avatar aliasname: i_aliasname username: i_username onShowUserInfo: { messageMain.showUserInfo(i_own_username) } visible: !i_groupable } ColumnLayout { Layout.fillHeight: true spacing: Kirigami.Units.smallSpacing / 2 // reduce spacing a little RowLayout { QQC2.Label { id: usernameLabel Layout.alignment: Qt.AlignLeft font.bold: true text: i_aliasname + ' ' + i_usernameurl + (i_editedByUserName === "" ? "" : " " + i18n("(edited by %1)", i_editedByUserName)) height: avatarRect.height onLinkActivated: messageMain.linkActivated(link) MouseArea { anchors.fill: parent acceptedButtons: Qt.RightButton onClicked: { if (i_useMenuMessage) { if (mouse.button === Qt.RightButton) { messageMenuLoader.posX = mouse.x messageMenuLoader.posY = mouse.y if (messageMenuLoader.active) messageMenuLoader.active = false else messageMenuLoader.active = true } } } } visible: !i_groupable } Kirigami.Icon { id: rolesInfo property var opacityDefaultValue: 0.5 source: "documentinfo" width: height height: 18 visible: i_roles.length > 0 opacity: opacityDefaultValue MouseArea { hoverEnabled: true anchors.fill: parent onEntered: { rolesInfo.opacity = 1.0 } onExited: { rolesInfo.opacity = rolesInfo.opacityDefaultValue } QQC2.ToolTip { id: tooltipRoleInfo text: i_roles } } } } Column { id: fullTextColumn Layout.fillWidth: true QQC2.Label { id: threadPreview width: parent.width visible: i_threadPreview.length > 0 textFormat: Text.RichText color: "red" //Convert to kirigami color font.pointSize: textLabel.font.pointSize - 1 text: i_threadPreview wrapMode: QQC2.Label.Wrap MouseArea { anchors.fill: parent acceptedButtons: Qt.RightButton | Qt.LeftButton onClicked: { //console.log("open thread " + i_tmid) messageMain.openThread(i_tmid, i_threadPreview) } } } QQC2.Label { id: textLabel width: parent.width textFormat: Text.RichText text: i_messageText wrapMode: QQC2.Label.Wrap onLinkActivated: messageMain.linkActivated(link) MouseArea { anchors.fill: parent acceptedButtons: Qt.RightButton onClicked: { if (i_useMenuMessage) { if (mouse.button === Qt.RightButton) { messageMenuLoader.posX = mouse.x messageMenuLoader.posY = mouse.y if (messageMenuLoader.active) messageMenuLoader.active = false else messageMenuLoader.active = true } } } } } Column { id: urlColumn width: parent.width //TODO //Reactivate when we have a parsed url ! //see info about bugs // Repeater { // id: repeaterUrl // model: i_urls // Text { // //Display it only if url != text otherwise it's not necessary // visible: model.modelData.url !== i_originalMessage // width: urlColumn.width // text: model.modelData.description === "" ? // RuqolaUtils.markdownToRichText(model.modelData.url) : // RuqolaUtils.markdownToRichText(model.modelData.description) // wrapMode: QQC2.Label.Wrap // textFormat: Text.RichText // onLinkActivated: messageMain.linkActivated(link) // } // } RowLayout { Layout.fillWidth: true RepeaterReactions { id: repearterReactions model: i_reactions + onAddReaction: { + messageMain.addReaction(i_messageID, emoji) + } onDeleteReaction: { messageMain.deleteReaction(i_messageID, emoji) } } } Repeater { id: repearterAttachments model: i_attachments Column { Text { visible: model.modelData.authorName !== "" width: urlColumn.width text: model.modelData.authorName wrapMode: QQC2.Label.Wrap anchors.leftMargin: Kirigami.Units.smallSpacing anchors.rightMargin: Kirigami.Units.smallSpacing } Row { QQC2.Label { id: attachmentTitle textFormat: Text.RichText visible: model.modelData.title !== "" width: urlColumn.width text: model.modelData.displayTitle wrapMode: QQC2.Label.Wrap anchors.leftMargin: Kirigami.Units.smallSpacing anchors.rightMargin: Kirigami.Units.smallSpacing onLinkActivated: { messageMain.linkActivated(link) } } DownloadButton { id: downloadButton visible: model.modelData.canDownloadAttachment onDownloadButtonClicked: { messageMain.downloadAttachment(model.modelData.link) } } Item { Layout.fillWidth: true } } QQC2.Label { visible: model.modelData.description !== "" width: urlColumn.width text: model.modelData.description wrapMode: QQC2.Label.Wrap anchors.leftMargin: Kirigami.Units.smallSpacing anchors.rightMargin: Kirigami.Units.smallSpacing } } } } } ThreadLabel { onOpenThread: { console.log(RuqolaDebugCategorySingleton.category, " OPen thread " + i_messageID) messageMain.openThread(i_messageID, i_messageText) } } } ReactionsPopup { visible: i_useMenuMessage onInsertReaction: { messageMain.addReaction(i_messageID, emoji) } } TimestampText { id: timestampText timestamp: i_timestamp visible: !i_groupable } } }