matrixion/resources/qml/MessageView.qml

875 lines
32 KiB
QML
Raw Normal View History

2021-03-05 02:35:15 +03:00
// SPDX-FileCopyrightText: 2021 Nheko Contributors
// SPDX-FileCopyrightText: 2022 Nheko Contributors
// SPDX-FileCopyrightText: 2023 Nheko Contributors
//
2021-03-05 02:35:15 +03:00
// SPDX-License-Identifier: GPL-3.0-or-later
2021-12-13 02:43:05 +03:00
import "./components"
import "./delegates"
import "./emoji"
import "./ui"
import "./dialogs"
import Qt.labs.platform 1.1 as Platform
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.2
import QtQuick.Window 2.13
import im.nheko 1.0
2021-01-17 06:05:02 +03:00
Item {
id: chatRoot
property int padding: Nheko.paddingMedium
property int availableWidth: width
2022-10-06 22:59:59 +03:00
property string searchString: ""
// HACK: https://bugreports.qt.io/browse/QTBUG-83972, qtwayland cannot auto hide menu
Connections {
function onHideMenu() {
messageContextMenu.close()
2023-01-31 21:34:26 +03:00
replyContextMenu.close()
}
target: MainWindow
}
ScrollBar {
id: scrollbar
parent: chat.parent
anchors.top: parent.top
anchors.right: parent.right
anchors.bottom: parent.bottom
}
2022-02-20 12:09:22 +03:00
ListView {
id: chat
anchors.fill: parent
2022-02-20 15:51:07 +03:00
property int delegateMaxWidth: ((Settings.timelineMaxWidth > 100 && Settings.timelineMaxWidth < chatRoot.availableWidth) ? Settings.timelineMaxWidth : chatRoot.availableWidth) - chatRoot.padding * 2 - (scrollbar.interactive? scrollbar.width : 0)
2022-02-20 12:09:22 +03:00
readonly property alias filteringInProgress: filteredTimeline.filteringInProgress
2022-02-20 12:09:22 +03:00
displayMarginBeginning: height / 2
displayMarginEnd: height / 2
TimelineFilter {
id: filteredTimeline
source: room
filterByThread: room ? room.thread : ""
2022-10-06 22:59:59 +03:00
filterByContent: chatRoot.searchString
}
2022-10-06 22:59:59 +03:00
model: (filteredTimeline.filterByThread || filteredTimeline.filterByContent) ? filteredTimeline : room
2022-02-20 12:09:22 +03:00
// reuseItems still has a few bugs, see https://bugreports.qt.io/browse/QTBUG-95105 https://bugreports.qt.io/browse/QTBUG-95107
//onModelChanged: if (room) room.sendReset()
//reuseItems: true
boundsBehavior: Flickable.StopAtBounds
//pixelAligned: true
spacing: 2
verticalLayoutDirection: ListView.BottomToTop
onCountChanged: {
// Mark timeline as read
if (atYEnd && room) model.currentIndex = 0;
}
2022-02-20 12:09:22 +03:00
ScrollBar.vertical: scrollbar
2022-02-20 15:51:07 +03:00
anchors.rightMargin: scrollbar.interactive? scrollbar.width : 0
2021-12-13 02:43:05 +03:00
2022-03-08 01:16:18 +03:00
Control {
2022-02-20 12:09:22 +03:00
id: messageActions
2021-12-13 02:43:05 +03:00
2022-02-20 12:09:22 +03:00
property Item attached: null
property alias model: row.model
// use comma to update on scroll
2022-03-08 01:16:18 +03:00
property var attachedPos: chat.contentY, attached ? chat.mapFromItem(attached, attached ? attached.width - width : 0, -height) : null
padding: Nheko.paddingSmall
2021-12-13 02:43:05 +03:00
hoverEnabled: true
2022-03-08 01:16:18 +03:00
visible: Settings.buttonsInTimeline && !!attached && (attached.hovered || hovered)
2022-02-20 12:09:22 +03:00
x: attached ? attachedPos.x : 0
y: attached ? attachedPos.y + Nheko.paddingSmall : 0
2022-02-20 12:09:22 +03:00
z: 10
2022-03-08 01:16:18 +03:00
background: Rectangle {
2022-03-08 01:19:56 +03:00
color: Nheko.colors.window
border.color: Nheko.colors.buttonText
border.width: 1
radius: padding
2022-02-20 12:09:22 +03:00
}
contentItem: RowLayout {
2022-02-20 12:09:22 +03:00
id: row
2022-02-20 12:09:22 +03:00
property var model
2022-02-20 12:09:22 +03:00
spacing: messageActions.padding
2022-02-20 12:09:22 +03:00
Repeater {
model: Settings.recentReactions
2022-02-20 12:09:22 +03:00
delegate: TextButton {
required property string modelData
visible: room ? room.permissions.canSend(MtxEvent.Reaction) : false
2021-12-13 02:43:05 +03:00
Layout.preferredHeight: fontMetrics.height
2022-02-20 12:09:22 +03:00
font.family: Settings.emojiFont
2022-02-20 12:09:22 +03:00
text: modelData
onClicked: {
2022-02-20 12:09:22 +03:00
room.input.reaction(row.model.eventId, modelData);
TimelineManager.focusMessageInput();
}
}
2022-02-20 12:09:22 +03:00
}
2022-02-20 12:09:22 +03:00
ImageButton {
id: editButton
visible: !!row.model && row.model.isEditable
buttonTextColor: Nheko.colors.buttonText
width: 16
hoverEnabled: true
image: ":/icons/icons/ui/edit.svg"
ToolTip.visible: hovered
ToolTip.delay: Nheko.tooltipDelay
ToolTip.text: qsTr("Edit")
onClicked: {
if (row.model.isEditable) room.edit = row.model.eventId;
}
2022-02-20 12:09:22 +03:00
}
2022-02-20 12:09:22 +03:00
ImageButton {
id: reactButton
visible: room ? room.permissions.canSend(MtxEvent.Reaction) : false
2022-02-20 12:09:22 +03:00
width: 16
hoverEnabled: true
image: ":/icons/icons/ui/smile-add.svg"
2022-02-20 12:09:22 +03:00
ToolTip.visible: hovered
ToolTip.delay: Nheko.tooltipDelay
ToolTip.text: qsTr("React")
onClicked: emojiPopup.visible ? emojiPopup.close() : emojiPopup.show(reactButton, function(emoji) {
var event_id = row.model ? row.model.eventId : "";
room.input.reaction(event_id, emoji);
TimelineManager.focusMessageInput();
})
}
2022-09-30 04:27:05 +03:00
ImageButton {
id: threadButton
visible: room ? room.permissions.canSend(MtxEvent.TextMessage) : false
2022-09-30 04:27:05 +03:00
width: 16
hoverEnabled: true
image: (row.model && row.model.threadId) ? ":/icons/icons/ui/thread.svg" : ":/icons/icons/ui/new-thread.svg"
2022-09-30 04:27:05 +03:00
ToolTip.visible: hovered
ToolTip.delay: Nheko.tooltipDelay
ToolTip.text: (row.model && row.model.threadId) ? qsTr("Reply in thread") : qsTr("New thread")
onClicked: room.thread = (row.model.threadId || row.model.eventId)
2022-09-30 04:27:05 +03:00
}
2022-02-20 12:09:22 +03:00
ImageButton {
id: replyButton
visible: room ? room.permissions.canSend(MtxEvent.TextMessage) : false
2022-02-20 12:09:22 +03:00
width: 16
hoverEnabled: true
image: ":/icons/icons/ui/reply.svg"
ToolTip.visible: hovered
ToolTip.delay: Nheko.tooltipDelay
ToolTip.text: qsTr("Reply")
onClicked: room.reply = row.model.eventId
2022-02-20 12:09:22 +03:00
}
2022-02-20 12:09:22 +03:00
ImageButton {
id: optionsButton
2022-02-20 12:09:22 +03:00
width: 16
hoverEnabled: true
image: ":/icons/icons/ui/options.svg"
ToolTip.visible: hovered
ToolTip.delay: Nheko.tooltipDelay
ToolTip.text: qsTr("Options")
2022-09-30 04:27:05 +03:00
onClicked: messageContextMenu.show(row.model.eventId, row.model.threadId, row.model.type, row.model.isSender, row.model.isEncrypted, row.model.isEditable, "", row.model.body, optionsButton)
}
}
2022-02-20 12:09:22 +03:00
}
2022-02-20 12:09:22 +03:00
ScrollHelper {
flickable: parent
anchors.fill: parent
}
Shortcut {
sequence: StandardKey.MoveToPreviousPage
onActivated: {
chat.contentY = chat.contentY - chat.height / 2;
chat.returnToBounds();
}
2022-02-20 12:09:22 +03:00
}
2022-02-20 12:09:22 +03:00
Shortcut {
sequence: StandardKey.MoveToNextPage
onActivated: {
chat.contentY = chat.contentY + chat.height / 2;
chat.returnToBounds();
}
2022-02-20 12:09:22 +03:00
}
2022-02-20 12:09:22 +03:00
Shortcut {
sequence: StandardKey.Cancel
onActivated: {
if(room.input.uploads.length > 0)
room.input.declineUploads();
else if(room.reply)
room.reply = undefined;
else if (room.edit)
room.edit = undefined;
2022-09-30 04:27:05 +03:00
else
room.thread = undefined
TimelineManager.focusMessageInput();
}
2022-02-20 12:09:22 +03:00
}
// These shortcuts use the room timeline because switching to threads and out is annoying otherwise.
// Better solution welcome.
2022-02-20 12:09:22 +03:00
Shortcut {
sequence: "Alt+Up"
onActivated: room.reply = room.indexToId(room.reply ? room.idToIndex(room.reply) + 1 : 0)
2022-02-20 12:09:22 +03:00
}
2021-04-27 13:09:00 +03:00
2022-02-20 12:09:22 +03:00
Shortcut {
sequence: "Alt+Down"
onActivated: {
var idx = room.reply ? room.idToIndex(room.reply) - 1 : -1;
room.reply = idx >= 0 ? room.indexToId(idx) : null;
}
2022-02-20 12:09:22 +03:00
}
2022-02-20 12:09:22 +03:00
Shortcut {
sequence: "Alt+F"
onActivated: {
if (room.reply) {
2022-02-20 12:09:22 +03:00
var forwardMess = forwardCompleterComponent.createObject(timelineRoot);
forwardMess.setMessageEventId(room.reply);
2022-02-20 12:09:22 +03:00
forwardMess.open();
room.reply = null;
timelineRoot.destroyOnClose(forwardMess);
}
}
2022-02-20 12:09:22 +03:00
}
2022-02-20 12:09:22 +03:00
Shortcut {
sequence: "Ctrl+E"
onActivated: {
room.edit = room.reply;
}
2022-02-20 12:09:22 +03:00
}
2022-05-06 01:36:38 +03:00
Window.onActiveChanged: readTimer.running = Window.active
2022-02-20 12:09:22 +03:00
Timer {
id: readTimer
2022-02-20 12:09:22 +03:00
// force current read index to update
onTriggered: {
if (room)
room.setCurrentIndex(room.currentIndex);
}
2022-02-20 12:09:22 +03:00
interval: 1000
}
2022-02-20 12:09:22 +03:00
Component {
id: sectionHeader
Column {
topPadding: userName_.visible? 4: 0
bottomPadding: Settings.bubbles? (isSender && previousMessageDay == day? 0 : 2) : 3
2022-02-20 12:09:22 +03:00
spacing: 8
visible: (previousMessageUserId !== userId || previousMessageDay !== day || isStateEvent !== previousMessageIsStateEvent)
width: parentWidth
height: ((previousMessageDay !== day) ? dateBubble.height : 0) + (isStateEvent? 0 : userName.height +8 )
Label {
id: dateBubble
anchors.horizontalCenter: parent ? parent.horizontalCenter : undefined
visible: room && previousMessageDay !== day
text: room ? room.formatDateSeparator(timestamp) : ""
color: Nheko.colors.text
height: Math.round(fontMetrics.height * 1.4)
width: contentWidth * 1.2
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
background: Rectangle {
radius: parent.height / 2
color: Nheko.colors.window
}
2022-02-20 12:09:22 +03:00
}
Row {
height: userName_.height
spacing: 8
2022-02-20 12:09:22 +03:00
visible: !isStateEvent && (!isSender || !Settings.bubbles)
id: userInfo
2022-02-20 12:09:22 +03:00
Avatar {
id: messageUserAvatar
width: Nheko.avatarSize * (Settings.smallAvatars? 0.5 : 1)
height: Nheko.avatarSize * (Settings.smallAvatars? 0.5 : 1)
url: !room ? "" : room.avatarUrl(userId).replace("mxc://", "image://MxcImage/")
displayName: userName
userid: userId
onClicked: room.openUserProfile(userId)
ToolTip.visible: messageUserAvatar.hovered
2022-02-20 12:09:22 +03:00
ToolTip.delay: Nheko.tooltipDelay
ToolTip.text: userid
}
2022-02-20 12:09:22 +03:00
Connections {
function onRoomAvatarUrlChanged() {
messageUserAvatar.url = room.avatarUrl(userId).replace("mxc://", "image://MxcImage/");
2022-02-20 12:09:22 +03:00
}
2022-02-20 12:09:22 +03:00
function onScrollToIndex(index) {
chat.positionViewAtIndex(index, ListView.Center);
}
target: room
2022-02-20 12:09:22 +03:00
}
property int remainingWidth: chat.delegateMaxWidth - spacing - messageUserAvatar.width
AbstractButton {
id: userNameButton
contentItem: ElidedLabel {
id: userName_
fullText: userName
color: TimelineManager.userColor(userId, Nheko.colors.base)
textFormat: Text.RichText
elideWidth: Math.min(userInfo.remainingWidth-Math.min(statusMsg.implicitWidth,userInfo.remainingWidth/3), userName_.fullTextWidth)
}
ToolTip.visible: hovered
2022-02-20 12:09:22 +03:00
ToolTip.delay: Nheko.tooltipDelay
ToolTip.text: userId
onClicked: room.openUserProfile(userId)
leftInset: 0
rightInset: 0
leftPadding: 0
rightPadding: 0
2022-02-20 12:09:22 +03:00
CursorShape {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
}
2022-02-20 12:09:22 +03:00
}
2022-02-20 12:09:22 +03:00
Label {
id: statusMsg
anchors.baseline: userNameButton.baseline
2022-02-20 12:09:22 +03:00
color: Nheko.colors.buttonText
text: Presence.userStatus(userId)
textFormat: Text.PlainText
elide: Text.ElideRight
width: Math.min(implicitWidth, userInfo.remainingWidth - userName_.width - parent.spacing)
2022-02-20 12:09:22 +03:00
font.italic: true
font.pointSize: Math.floor(fontMetrics.font.pointSize * 0.8)
ToolTip.text: qsTr("%1's status message").arg(userName)
ToolTip.visible: statusMsgHoverHandler.hovered
ToolTip.delay: Nheko.tooltipDelay
HoverHandler {
id: statusMsgHoverHandler
}
2022-02-20 12:09:22 +03:00
Connections {
target: Presence
2022-02-20 12:09:22 +03:00
function onPresenceChanged(id) {
if (id == userId) statusMsg.text = Presence.userStatus(userId);
}
}
}
}
}
2022-02-20 12:09:22 +03:00
}
2022-03-24 03:35:42 +03:00
delegate: Item {
2022-02-20 12:09:22 +03:00
id: wrapper
required property double proportionalHeight
required property int type
required property string typeString
required property int originalWidth
required property string blurhash
required property string body
required property string formattedBody
required property string eventId
required property string filename
required property string filesize
required property string url
required property string thumbnailUrl
2022-03-21 02:48:27 +03:00
required property string duration
2022-02-20 12:09:22 +03:00
required property bool isOnlyEmoji
required property bool isSender
required property bool isEncrypted
required property bool isEditable
required property bool isEdited
required property bool isStateEvent
2022-10-07 12:11:07 +03:00
property bool previousMessageIsStateEvent: (index + 1) >= chat.count ? true : chat.model.dataByIndex(index+1, Room.IsStateEvent)
2022-02-20 12:09:22 +03:00
required property string replyTo
2022-09-30 04:27:05 +03:00
required property string threadId
2022-02-20 12:09:22 +03:00
required property string userId
required property string roomTopic
required property string roomName
required property string callType
required property var reactions
required property int trustlevel
required property int notificationlevel
2022-02-20 12:09:22 +03:00
required property int encryptionError
required property var timestamp
required property int status
required property int index
required property int relatedEventCacheBuster
2022-10-07 12:11:07 +03:00
required property var day
property string previousMessageUserId: (index + 1) >= chat.count ? "" : chat.model.dataByIndex(index+1, Room.UserId)
property var previousMessageDay: (index + 1) >= chat.count ? 0 : chat.model.dataByIndex(index+1, Room.Day)
2022-02-20 12:09:22 +03:00
required property string userName
property bool scrolledToThis: eventId === room.scrollTarget && (y + height > chat.y + chat.contentY && y < chat.y + chat.height + chat.contentY)
2022-02-20 12:09:22 +03:00
anchors.horizontalCenter: parent ? parent.horizontalCenter : undefined
width: chat.delegateMaxWidth
height: section.active ? section.height + timelinerow.height : timelinerow.height
2022-02-20 12:09:22 +03:00
Loader {
id: section
property int parentWidth: parent.width
property string userId: wrapper.userId
property string previousMessageUserId: wrapper.previousMessageUserId
2022-10-07 12:11:07 +03:00
property var day: wrapper.day
property var previousMessageDay: wrapper.previousMessageDay
2022-02-20 12:09:22 +03:00
property bool previousMessageIsStateEvent: wrapper.previousMessageIsStateEvent
property bool isStateEvent: wrapper.isStateEvent
property bool isSender: wrapper.isSender
property string userName: wrapper.userName
property date timestamp: wrapper.timestamp
z: 4
2022-10-07 12:11:07 +03:00
active: previousMessageUserId !== userId || previousMessageDay !== day || previousMessageIsStateEvent !== isStateEvent
2022-02-20 12:09:22 +03:00
//asynchronous: true
sourceComponent: sectionHeader
visible: status == Loader.Ready
}
2022-02-20 12:09:22 +03:00
TimelineRow {
id: timelinerow
proportionalHeight: wrapper.proportionalHeight
type: chat.model, wrapper.type
typeString: wrapper.typeString
originalWidth: wrapper.originalWidth
blurhash: wrapper.blurhash
body: wrapper.body
formattedBody: wrapper.formattedBody
eventId: chat.model, wrapper.eventId
filename: wrapper.filename
filesize: wrapper.filesize
url: wrapper.url
thumbnailUrl: wrapper.thumbnailUrl
2022-03-21 02:48:27 +03:00
duration: wrapper.duration
2022-02-20 12:09:22 +03:00
isOnlyEmoji: wrapper.isOnlyEmoji
isSender: wrapper.isSender
isEncrypted: wrapper.isEncrypted
isEditable: wrapper.isEditable
isEdited: wrapper.isEdited
isStateEvent: wrapper.isStateEvent
replyTo: wrapper.replyTo
2022-09-30 04:27:05 +03:00
threadId: wrapper.threadId
2022-02-20 12:09:22 +03:00
userId: wrapper.userId
userName: wrapper.userName
roomTopic: wrapper.roomTopic
roomName: wrapper.roomName
callType: wrapper.callType
reactions: wrapper.reactions
trustlevel: wrapper.trustlevel
notificationlevel: wrapper.notificationlevel
2022-02-20 12:09:22 +03:00
encryptionError: wrapper.encryptionError
timestamp: wrapper.timestamp
status: wrapper.status
index: wrapper.index
2022-02-20 12:09:22 +03:00
relatedEventCacheBuster: wrapper.relatedEventCacheBuster
y: section.visible && section.active ? section.y + section.height : 0
2022-03-08 01:16:18 +03:00
onHoveredChanged: {
2022-03-08 01:19:56 +03:00
if (!Settings.mobileMode && hovered) {
if (!messageActions.hovered) {
messageActions.attached = timelinerow;
messageActions.model = timelinerow;
}
}
}
2022-03-24 03:35:42 +03:00
background: Rectangle {
id: scrollHighlight
opacity: 0
visible: true
z: 1
enabled: false
color: Nheko.colors.highlight
states: State {
name: "revealed"
when: wrapper.scrolledToThis
}
2022-03-24 03:35:42 +03:00
transitions: Transition {
from: ""
to: "revealed"
SequentialAnimation {
PropertyAnimation {
target: scrollHighlight
properties: "opacity"
easing.type: Easing.InOutQuad
from: 0
to: 1
duration: 500
}
PropertyAnimation {
target: scrollHighlight
properties: "opacity"
easing.type: Easing.InOutQuad
from: 1
to: 0
duration: 500
}
ScriptAction {
script: room.eventShown()
2022-03-24 03:35:42 +03:00
}
}
}
}
2022-03-08 01:16:18 +03:00
}
2022-02-20 12:09:22 +03:00
Connections {
function onMovementEnded() {
if (y + height + 2 * chat.spacing > chat.contentY + chat.height && y < chat.contentY + chat.height)
2022-03-08 01:19:56 +03:00
chat.model.currentIndex = index;
}
2022-02-20 12:09:22 +03:00
target: chat
}
2022-02-20 12:09:22 +03:00
}
2022-02-20 12:09:22 +03:00
footer: Item {
anchors.horizontalCenter: parent.horizontalCenter
anchors.margins: Nheko.paddingLarge
visible: (room && room.paginationInProgress) || chat.filteringInProgress
2022-02-20 12:09:22 +03:00
// hacky, but works
height: loadingSpinner.height + 2 * Nheko.paddingLarge
Spinner {
id: loadingSpinner
anchors.centerIn: parent
anchors.margins: Nheko.paddingLarge
running: (room && room.paginationInProgress) || chat.filteringInProgress
2022-02-20 12:09:22 +03:00
foreground: Nheko.colors.mid
z: 3
2021-07-22 03:37:36 +03:00
}
2021-07-22 14:55:12 +03:00
}
}
Platform.Menu {
id: messageContextMenu
property string eventId
2022-09-30 04:27:05 +03:00
property string threadId
property string link
property string text
property int eventType
property bool isEncrypted
property bool isEditable
property bool isSender
2022-09-30 04:27:05 +03:00
function show(eventId_, threadId_, eventType_, isSender_, isEncrypted_, isEditable_, link_, text_, showAt_) {
eventId = eventId_;
2022-09-30 04:27:05 +03:00
threadId = threadId_;
eventType = eventType_;
isEncrypted = isEncrypted_;
isEditable = isEditable_;
isSender = isSender_;
if (text_)
text = text_;
else
text = "";
if (link_)
link = link_;
else
link = "";
if (showAt_)
open(showAt_);
else
open();
}
Component {
id: removeReason
InputDialog {
id: removeReasonDialog
property string eventId
title: qsTr("Reason for removal")
prompt: qsTr("Enter reason for removal or hit enter for no reason:")
onAccepted: function(text) {
room.redactEvent(eventId, text);
}
}
}
Platform.MenuItem {
visible: messageContextMenu.text
enabled: visible
text: qsTr("&Copy")
onTriggered: Clipboard.text = messageContextMenu.text
}
Platform.MenuItem {
visible: messageContextMenu.link
enabled: visible
text: qsTr("Copy &link location")
onTriggered: Clipboard.text = messageContextMenu.link
}
Platform.MenuItem {
id: reactionOption
visible: room ? room.permissions.canSend(MtxEvent.Reaction) : false
text: qsTr("Re&act")
onTriggered: emojiPopup.show(null, function(emoji) {
room.input.reaction(messageContextMenu.eventId, emoji);
})
}
Platform.MenuItem {
visible: room ? room.permissions.canSend(MtxEvent.TextMessage) : false
text: qsTr("Repl&y")
2022-09-30 04:27:05 +03:00
onTriggered: room.reply = (messageContextMenu.eventId)
}
Platform.MenuItem {
visible: messageContextMenu.isEditable && (room ? room.permissions.canSend(MtxEvent.TextMessage) : false)
enabled: visible
text: qsTr("&Edit")
2022-09-30 04:27:05 +03:00
onTriggered: room.edit = (messageContextMenu.eventId)
}
Platform.MenuItem {
visible: (room ? room.permissions.canSend(MtxEvent.TextMessage) : false)
enabled: visible
text: qsTr("&Thread")
onTriggered: room.thread = (messageContextMenu.threadId || messageContextMenu.eventId)
}
2021-12-11 08:10:41 +03:00
Platform.MenuItem {
visible: (room ? room.permissions.canChange(MtxEvent.PinnedEvents) : false)
enabled: visible
text: visible && room.pinnedMessages.includes(messageContextMenu.eventId) ? qsTr("Un&pin") : qsTr("&Pin")
onTriggered: visible && room.pinnedMessages.includes(messageContextMenu.eventId) ? room.unpin(messageContextMenu.eventId) : room.pin(messageContextMenu.eventId)
}
Platform.MenuItem {
2022-09-30 04:27:05 +03:00
text: qsTr("&Read receipts")
2021-07-24 21:42:40 +03:00
onTriggered: room.showReadReceipts(messageContextMenu.eventId)
}
Platform.MenuItem {
visible: messageContextMenu.eventType == MtxEvent.ImageMessage || messageContextMenu.eventType == MtxEvent.VideoMessage || messageContextMenu.eventType == MtxEvent.AudioMessage || messageContextMenu.eventType == MtxEvent.FileMessage || messageContextMenu.eventType == MtxEvent.Sticker || messageContextMenu.eventType == MtxEvent.TextMessage || messageContextMenu.eventType == MtxEvent.LocationMessage || messageContextMenu.eventType == MtxEvent.EmoteMessage || messageContextMenu.eventType == MtxEvent.NoticeMessage
text: qsTr("&Forward")
onTriggered: {
var forwardMess = forwardCompleterComponent.createObject(timelineRoot);
forwardMess.setMessageEventId(messageContextMenu.eventId);
forwardMess.open();
timelineRoot.destroyOnClose(forwardMess);
}
}
Platform.MenuItem {
text: qsTr("&Mark as read")
}
Platform.MenuItem {
text: qsTr("View raw message")
onTriggered: room.viewRawMessage(messageContextMenu.eventId)
}
Platform.MenuItem {
// TODO(Nico): Fix this still being iterated over, when using keyboard to select options
visible: messageContextMenu.isEncrypted
enabled: visible
text: qsTr("View decrypted raw message")
onTriggered: room.viewDecryptedRawMessage(messageContextMenu.eventId)
}
Platform.MenuItem {
visible: (room ? room.permissions.canRedact() : false) || messageContextMenu.isSender
text: qsTr("Remo&ve message")
onTriggered: function() {
var dialog = removeReason.createObject(timelineRoot);
dialog.eventId = messageContextMenu.eventId;
dialog.show();
dialog.forceActiveFocus();
timelineRoot.destroyOnClose(dialog);
}
}
Platform.MenuItem {
visible: messageContextMenu.eventType == MtxEvent.ImageMessage || messageContextMenu.eventType == MtxEvent.VideoMessage || messageContextMenu.eventType == MtxEvent.AudioMessage || messageContextMenu.eventType == MtxEvent.FileMessage || messageContextMenu.eventType == MtxEvent.Sticker
enabled: visible
text: qsTr("&Save as")
onTriggered: room.saveMedia(messageContextMenu.eventId)
}
Platform.MenuItem {
visible: messageContextMenu.eventType == MtxEvent.ImageMessage || messageContextMenu.eventType == MtxEvent.VideoMessage || messageContextMenu.eventType == MtxEvent.AudioMessage || messageContextMenu.eventType == MtxEvent.FileMessage || messageContextMenu.eventType == MtxEvent.Sticker
enabled: visible
text: qsTr("&Open in external program")
onTriggered: room.openMedia(messageContextMenu.eventId)
}
Platform.MenuItem {
visible: messageContextMenu.eventId
enabled: visible
text: qsTr("Copy link to eve&nt")
onTriggered: room.copyLinkToEvent(messageContextMenu.eventId)
}
}
Component {
id: forwardCompleterComponent
ForwardCompleter {
}
}
2021-08-25 17:10:55 +03:00
Platform.Menu {
id: replyContextMenu
property string text
property string link
2022-03-24 03:35:42 +03:00
property string eventId
2021-08-25 17:10:55 +03:00
2022-03-24 03:35:42 +03:00
function show(text_, link_, eventId_) {
2021-08-25 17:10:55 +03:00
text = text_;
link = link_;
2022-03-24 03:35:42 +03:00
eventId = eventId_;
2021-08-25 17:10:55 +03:00
open();
}
Platform.MenuItem {
visible: replyContextMenu.text
enabled: visible
text: qsTr("&Copy")
onTriggered: Clipboard.text = replyContextMenu.text
}
Platform.MenuItem {
visible: replyContextMenu.link
enabled: visible
text: qsTr("Copy &link location")
onTriggered: Clipboard.text = replyContextMenu.link
}
Platform.MenuItem {
visible: true
enabled: visible
2021-09-14 00:17:03 +03:00
text: qsTr("&Go to quoted message")
onTriggered: room.showEvent(replyContextMenu.eventId)
2021-08-25 17:10:55 +03:00
}
2021-08-25 17:10:55 +03:00
}
2022-03-30 00:11:25 +03:00
RoundButton {
id: toEndButton
anchors {
bottom: parent.bottom
right: scrollbar.left
bottomMargin: Nheko.paddingMedium+(fullWidth-width)/2
rightMargin: Nheko.paddingMedium+(fullWidth-width)/2
}
property int fullWidth: 40
width: 0
2022-03-30 00:11:25 +03:00
height: width
radius: width/2
onClicked: chat.positionViewAtBeginning();
flat: true
hoverEnabled: true
background: Rectangle {
color: toEndButton.down ? Nheko.colors.highlight : Nheko.colors.button
opacity: enabled ? 1 : 0.3
border.color: toEndButton.hovered ? Nheko.colors.highlight : Nheko.colors.buttonText
border.width: 1
radius: toEndButton.radius
}
2022-03-30 00:11:25 +03:00
states: [
State {
name: ""
PropertyChanges { target: toEndButton; width: 0 }
},
State {
name: "shown"
when: !chat.atYEnd
PropertyChanges { target: toEndButton; width: toEndButton.fullWidth }
}
]
2022-03-30 00:11:25 +03:00
Image {
id: buttonImg
anchors.fill: parent
anchors.margins: Nheko.paddingMedium
source: "image://colorimage/:/icons/icons/ui/download.svg?" + (toEndButton.down ? Nheko.colors.highlightedText : Nheko.colors.buttonText)
2022-03-30 00:11:25 +03:00
fillMode: Image.PreserveAspectFit
}
transitions: Transition {
from: ""
to: "shown"
reversible: true
2022-03-30 00:11:25 +03:00
SequentialAnimation {
PauseAnimation { duration: 500 }
2022-03-30 00:11:25 +03:00
PropertyAnimation {
target: toEndButton
properties: "width"
easing.type: Easing.InOutQuad
duration: 200
}
}
}
2022-03-30 00:11:25 +03:00
}
}