|
|
|
@ -31,334 +31,329 @@ Item { |
|
|
|
|
|
|
|
|
|
target: MainWindow |
|
|
|
|
} |
|
|
|
|
ScrollBar { |
|
|
|
|
id: scrollbar |
|
|
|
|
|
|
|
|
|
anchors.bottom: parent.bottom |
|
|
|
|
anchors.right: parent.right |
|
|
|
|
anchors.top: parent.top |
|
|
|
|
parent: chat.parent |
|
|
|
|
} |
|
|
|
|
ListView { |
|
|
|
|
id: chat |
|
|
|
|
|
|
|
|
|
property int delegateMaxWidth: ((Settings.timelineMaxWidth > 100 && Settings.timelineMaxWidth < chatRoot.availableWidth) ? Settings.timelineMaxWidth : chatRoot.availableWidth) - chatRoot.padding * 2 - (scrollbar.interactive ? scrollbar.width : 0) |
|
|
|
|
readonly property alias filteringInProgress: filteredTimeline.filteringInProgress |
|
|
|
|
|
|
|
|
|
ScrollBar.vertical: scrollbar |
|
|
|
|
ScrollView { |
|
|
|
|
id: scrollView |
|
|
|
|
anchors.fill: parent |
|
|
|
|
anchors.rightMargin: scrollbar.interactive ? scrollbar.width : 0 |
|
|
|
|
// 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 |
|
|
|
|
displayMarginBeginning: height / 4 |
|
|
|
|
displayMarginEnd: height / 4 |
|
|
|
|
model: (filteredTimeline.filterByThread || filteredTimeline.filterByContent) ? filteredTimeline : room |
|
|
|
|
//pixelAligned: true |
|
|
|
|
spacing: 2 |
|
|
|
|
verticalLayoutDirection: ListView.BottomToTop |
|
|
|
|
|
|
|
|
|
Component { |
|
|
|
|
id: defaultMessageStyle |
|
|
|
|
|
|
|
|
|
TimelineDefaultMessageStyle { |
|
|
|
|
messageActions: messageActionsC |
|
|
|
|
messageContextMenu: messageContextMenuC |
|
|
|
|
replyContextMenu: replyContextMenuC |
|
|
|
|
scrolledToThis: eventId === room.scrollTarget && (y + height > chat.y + chat.contentY && y < chat.y + chat.height + chat.contentY) |
|
|
|
|
ListView { |
|
|
|
|
id: chat |
|
|
|
|
|
|
|
|
|
property int delegateMaxWidth: ((Settings.timelineMaxWidth > 100 && Settings.timelineMaxWidth < chatRoot.availableWidth) ? Settings.timelineMaxWidth : chatRoot.availableWidth) - chatRoot.padding * 2 - scrollView.effectiveScrollBarWidth |
|
|
|
|
readonly property alias filteringInProgress: filteredTimeline.filteringInProgress |
|
|
|
|
|
|
|
|
|
// 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 |
|
|
|
|
displayMarginBeginning: height / 4 |
|
|
|
|
displayMarginEnd: height / 4 |
|
|
|
|
model: (filteredTimeline.filterByThread || filteredTimeline.filterByContent) ? filteredTimeline : room |
|
|
|
|
//pixelAligned: true |
|
|
|
|
spacing: 2 |
|
|
|
|
verticalLayoutDirection: ListView.BottomToTop |
|
|
|
|
|
|
|
|
|
Component { |
|
|
|
|
id: defaultMessageStyle |
|
|
|
|
|
|
|
|
|
TimelineDefaultMessageStyle { |
|
|
|
|
messageActions: messageActionsC |
|
|
|
|
messageContextMenu: messageContextMenuC |
|
|
|
|
replyContextMenu: replyContextMenuC |
|
|
|
|
scrolledToThis: eventId === room.scrollTarget && (y + height > chat.y + chat.contentY && y < chat.y + chat.height + chat.contentY) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
Component { |
|
|
|
|
id: bubbleMessageStyle |
|
|
|
|
|
|
|
|
|
TimelineBubbleMessageStyle { |
|
|
|
|
messageActions: messageActionsC |
|
|
|
|
messageContextMenu: messageContextMenuC |
|
|
|
|
replyContextMenu: replyContextMenuC |
|
|
|
|
scrolledToThis: eventId === room.scrollTarget && (y + height > chat.y + chat.contentY && y < chat.y + chat.height + chat.contentY) |
|
|
|
|
Component { |
|
|
|
|
id: bubbleMessageStyle |
|
|
|
|
|
|
|
|
|
TimelineBubbleMessageStyle { |
|
|
|
|
messageActions: messageActionsC |
|
|
|
|
messageContextMenu: messageContextMenuC |
|
|
|
|
replyContextMenu: replyContextMenuC |
|
|
|
|
scrolledToThis: eventId === room.scrollTarget && (y + height > chat.y + chat.contentY && y < chat.y + chat.height + chat.contentY) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
delegate: Settings.bubbles ? bubbleMessageStyle : defaultMessageStyle |
|
|
|
|
footer: Item { |
|
|
|
|
anchors.horizontalCenter: parent.horizontalCenter |
|
|
|
|
anchors.margins: Nheko.paddingLarge |
|
|
|
|
// hacky, but works |
|
|
|
|
height: loadingSpinner.height + 2 * Nheko.paddingLarge |
|
|
|
|
visible: (room && room.paginationInProgress) || chat.filteringInProgress |
|
|
|
|
|
|
|
|
|
Spinner { |
|
|
|
|
id: loadingSpinner |
|
|
|
|
|
|
|
|
|
anchors.centerIn: parent |
|
|
|
|
delegate: Settings.bubbles ? bubbleMessageStyle : defaultMessageStyle |
|
|
|
|
footer: Item { |
|
|
|
|
anchors.horizontalCenter: parent.horizontalCenter |
|
|
|
|
anchors.margins: Nheko.paddingLarge |
|
|
|
|
foreground: palette.mid |
|
|
|
|
running: (room && room.paginationInProgress) || chat.filteringInProgress |
|
|
|
|
z: 3 |
|
|
|
|
// hacky, but works |
|
|
|
|
height: loadingSpinner.height + 2 * Nheko.paddingLarge |
|
|
|
|
visible: (room && room.paginationInProgress) || chat.filteringInProgress |
|
|
|
|
|
|
|
|
|
Spinner { |
|
|
|
|
id: loadingSpinner |
|
|
|
|
|
|
|
|
|
anchors.centerIn: parent |
|
|
|
|
anchors.margins: Nheko.paddingLarge |
|
|
|
|
foreground: palette.mid |
|
|
|
|
running: (room && room.paginationInProgress) || chat.filteringInProgress |
|
|
|
|
z: 3 |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
Window.onActiveChanged: readTimer.running = Window.active |
|
|
|
|
onCountChanged: { |
|
|
|
|
// Mark timeline as read |
|
|
|
|
if (atYEnd && room) |
|
|
|
|
Window.onActiveChanged: readTimer.running = Window.active |
|
|
|
|
onCountChanged: { |
|
|
|
|
// Mark timeline as read |
|
|
|
|
if (atYEnd && room) |
|
|
|
|
model.currentIndex = 0; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
TimelineFilter { |
|
|
|
|
id: filteredTimeline |
|
|
|
|
|
|
|
|
|
filterByContent: chatRoot.searchString |
|
|
|
|
filterByThread: room ? room.thread : "" |
|
|
|
|
source: room |
|
|
|
|
} |
|
|
|
|
Control { |
|
|
|
|
id: messageActionsC |
|
|
|
|
|
|
|
|
|
property Item attached: null |
|
|
|
|
// use comma to update on scroll |
|
|
|
|
property alias model: row.model |
|
|
|
|
|
|
|
|
|
hoverEnabled: true |
|
|
|
|
padding: Nheko.paddingSmall |
|
|
|
|
visible: Settings.buttonsInTimeline && !!attached && (attached.hovered || hovered) |
|
|
|
|
z: 10 |
|
|
|
|
parent: chat.contentItem |
|
|
|
|
anchors.bottom: attached?.top |
|
|
|
|
anchors.right: attached?.right |
|
|
|
|
|
|
|
|
|
background: Rectangle { |
|
|
|
|
border.color: palette.buttonText |
|
|
|
|
border.width: 1 |
|
|
|
|
color: palette.window |
|
|
|
|
radius: padding |
|
|
|
|
} |
|
|
|
|
contentItem: RowLayout { |
|
|
|
|
id: row |
|
|
|
|
|
|
|
|
|
property var model |
|
|
|
|
|
|
|
|
|
spacing: messageActionsC.padding |
|
|
|
|
|
|
|
|
|
Repeater { |
|
|
|
|
model: Settings.recentReactions |
|
|
|
|
visible: room ? room.permissions.canSend(MtxEvent.Reaction) : false |
|
|
|
|
|
|
|
|
|
delegate: AbstractButton { |
|
|
|
|
id: button |
|
|
|
|
TimelineFilter { |
|
|
|
|
id: filteredTimeline |
|
|
|
|
|
|
|
|
|
property color buttonTextColor: palette.buttonText |
|
|
|
|
property color highlightColor: palette.highlight |
|
|
|
|
required property string modelData |
|
|
|
|
property bool showImage: modelData.startsWith("mxc://") |
|
|
|
|
|
|
|
|
|
//Layout.preferredHeight: fontMetrics.height |
|
|
|
|
Layout.alignment: Qt.AlignBottom |
|
|
|
|
focusPolicy: Qt.NoFocus |
|
|
|
|
height: showImage ? 16 : buttonText.implicitHeight |
|
|
|
|
implicitHeight: showImage ? 16 : buttonText.implicitHeight |
|
|
|
|
implicitWidth: showImage ? 16 : buttonText.implicitWidth |
|
|
|
|
width: showImage ? 16 : buttonText.implicitWidth |
|
|
|
|
|
|
|
|
|
onClicked: { |
|
|
|
|
room.input.reaction(row.model.eventId, modelData); |
|
|
|
|
TimelineManager.focusMessageInput(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
Label { |
|
|
|
|
id: buttonText |
|
|
|
|
|
|
|
|
|
anchors.centerIn: parent |
|
|
|
|
color: button.hovered ? button.highlightColor : button.buttonTextColor |
|
|
|
|
font.family: Settings.emojiFont |
|
|
|
|
horizontalAlignment: Text.AlignHCenter |
|
|
|
|
padding: 0 |
|
|
|
|
text: button.modelData |
|
|
|
|
verticalAlignment: Text.AlignVCenter |
|
|
|
|
visible: !button.showImage |
|
|
|
|
} |
|
|
|
|
Image { |
|
|
|
|
id: buttonImg |
|
|
|
|
|
|
|
|
|
// Workaround, can't get icon.source working for now... |
|
|
|
|
anchors.fill: parent |
|
|
|
|
fillMode: Image.PreserveAspectFit |
|
|
|
|
source: button.showImage ? (button.modelData.replace("mxc://", "image://MxcImage/") + "?scale") : "" |
|
|
|
|
sourceSize.height: button.height |
|
|
|
|
sourceSize.width: button.width |
|
|
|
|
} |
|
|
|
|
NhekoCursorShape { |
|
|
|
|
anchors.fill: parent |
|
|
|
|
cursorShape: Qt.PointingHandCursor |
|
|
|
|
} |
|
|
|
|
Ripple { |
|
|
|
|
color: Qt.rgba(buttonTextColor.r, buttonTextColor.g, buttonTextColor.b, 0.5) |
|
|
|
|
filterByContent: chatRoot.searchString |
|
|
|
|
filterByThread: room ? room.thread : "" |
|
|
|
|
source: room |
|
|
|
|
} |
|
|
|
|
Control { |
|
|
|
|
id: messageActionsC |
|
|
|
|
|
|
|
|
|
property Item attached: null |
|
|
|
|
// use comma to update on scroll |
|
|
|
|
property alias model: row.model |
|
|
|
|
|
|
|
|
|
hoverEnabled: true |
|
|
|
|
padding: Nheko.paddingSmall |
|
|
|
|
visible: Settings.buttonsInTimeline && !!attached && (attached.hovered || hovered) |
|
|
|
|
z: 10 |
|
|
|
|
parent: chat.contentItem |
|
|
|
|
anchors.bottom: attached?.top |
|
|
|
|
anchors.right: attached?.right |
|
|
|
|
|
|
|
|
|
background: Rectangle { |
|
|
|
|
border.color: palette.buttonText |
|
|
|
|
border.width: 1 |
|
|
|
|
color: palette.window |
|
|
|
|
radius: padding |
|
|
|
|
} |
|
|
|
|
contentItem: RowLayout { |
|
|
|
|
id: row |
|
|
|
|
|
|
|
|
|
property var model |
|
|
|
|
|
|
|
|
|
spacing: messageActionsC.padding |
|
|
|
|
|
|
|
|
|
Repeater { |
|
|
|
|
model: Settings.recentReactions |
|
|
|
|
visible: room ? room.permissions.canSend(MtxEvent.Reaction) : false |
|
|
|
|
|
|
|
|
|
delegate: AbstractButton { |
|
|
|
|
id: button |
|
|
|
|
|
|
|
|
|
property color buttonTextColor: palette.buttonText |
|
|
|
|
property color highlightColor: palette.highlight |
|
|
|
|
required property string modelData |
|
|
|
|
property bool showImage: modelData.startsWith("mxc://") |
|
|
|
|
|
|
|
|
|
//Layout.preferredHeight: fontMetrics.height |
|
|
|
|
Layout.alignment: Qt.AlignBottom |
|
|
|
|
focusPolicy: Qt.NoFocus |
|
|
|
|
height: showImage ? 16 : buttonText.implicitHeight |
|
|
|
|
implicitHeight: showImage ? 16 : buttonText.implicitHeight |
|
|
|
|
implicitWidth: showImage ? 16 : buttonText.implicitWidth |
|
|
|
|
width: showImage ? 16 : buttonText.implicitWidth |
|
|
|
|
|
|
|
|
|
onClicked: { |
|
|
|
|
room.input.reaction(row.model.eventId, modelData); |
|
|
|
|
TimelineManager.focusMessageInput(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
Label { |
|
|
|
|
id: buttonText |
|
|
|
|
|
|
|
|
|
anchors.centerIn: parent |
|
|
|
|
color: button.hovered ? button.highlightColor : button.buttonTextColor |
|
|
|
|
font.family: Settings.emojiFont |
|
|
|
|
horizontalAlignment: Text.AlignHCenter |
|
|
|
|
padding: 0 |
|
|
|
|
text: button.modelData |
|
|
|
|
verticalAlignment: Text.AlignVCenter |
|
|
|
|
visible: !button.showImage |
|
|
|
|
} |
|
|
|
|
Image { |
|
|
|
|
id: buttonImg |
|
|
|
|
|
|
|
|
|
// Workaround, can't get icon.source working for now... |
|
|
|
|
anchors.fill: parent |
|
|
|
|
fillMode: Image.PreserveAspectFit |
|
|
|
|
source: button.showImage ? (button.modelData.replace("mxc://", "image://MxcImage/") + "?scale") : "" |
|
|
|
|
sourceSize.height: button.height |
|
|
|
|
sourceSize.width: button.width |
|
|
|
|
} |
|
|
|
|
NhekoCursorShape { |
|
|
|
|
anchors.fill: parent |
|
|
|
|
cursorShape: Qt.PointingHandCursor |
|
|
|
|
} |
|
|
|
|
Ripple { |
|
|
|
|
color: Qt.rgba(buttonTextColor.r, buttonTextColor.g, buttonTextColor.b, 0.5) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
ImageButton { |
|
|
|
|
ToolTip.delay: Nheko.tooltipDelay |
|
|
|
|
ToolTip.text: qsTr("Edit") |
|
|
|
|
ToolTip.visible: hovered |
|
|
|
|
buttonTextColor: palette.buttonText |
|
|
|
|
hoverEnabled: true |
|
|
|
|
image: ":/icons/icons/ui/edit.svg" |
|
|
|
|
visible: !!row.model && row.model.isEditable |
|
|
|
|
width: 16 |
|
|
|
|
|
|
|
|
|
onClicked: { |
|
|
|
|
if (row.model.isEditable) |
|
|
|
|
ImageButton { |
|
|
|
|
ToolTip.delay: Nheko.tooltipDelay |
|
|
|
|
ToolTip.text: qsTr("Edit") |
|
|
|
|
ToolTip.visible: hovered |
|
|
|
|
buttonTextColor: palette.buttonText |
|
|
|
|
hoverEnabled: true |
|
|
|
|
image: ":/icons/icons/ui/edit.svg" |
|
|
|
|
visible: !!row.model && row.model.isEditable |
|
|
|
|
width: 16 |
|
|
|
|
|
|
|
|
|
onClicked: { |
|
|
|
|
if (row.model.isEditable) |
|
|
|
|
room.edit = row.model.eventId; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
ImageButton { |
|
|
|
|
id: reactButton |
|
|
|
|
|
|
|
|
|
ToolTip.delay: Nheko.tooltipDelay |
|
|
|
|
ToolTip.text: qsTr("React") |
|
|
|
|
ToolTip.visible: hovered |
|
|
|
|
hoverEnabled: true |
|
|
|
|
image: ":/icons/icons/ui/smile-add.svg" |
|
|
|
|
visible: room ? room.permissions.canSend(MtxEvent.Reaction) : false |
|
|
|
|
width: 16 |
|
|
|
|
|
|
|
|
|
onClicked: emojiPopup.visible ? emojiPopup.close() : emojiPopup.show(reactButton, room.roomId, function (plaintext, markdown) { |
|
|
|
|
ImageButton { |
|
|
|
|
id: reactButton |
|
|
|
|
|
|
|
|
|
ToolTip.delay: Nheko.tooltipDelay |
|
|
|
|
ToolTip.text: qsTr("React") |
|
|
|
|
ToolTip.visible: hovered |
|
|
|
|
hoverEnabled: true |
|
|
|
|
image: ":/icons/icons/ui/smile-add.svg" |
|
|
|
|
visible: room ? room.permissions.canSend(MtxEvent.Reaction) : false |
|
|
|
|
width: 16 |
|
|
|
|
|
|
|
|
|
onClicked: emojiPopup.visible ? emojiPopup.close() : emojiPopup.show(reactButton, room.roomId, function (plaintext, markdown) { |
|
|
|
|
var event_id = row.model ? row.model.eventId : ""; |
|
|
|
|
room.input.reaction(event_id, plaintext); |
|
|
|
|
TimelineManager.focusMessageInput(); |
|
|
|
|
}) |
|
|
|
|
} |
|
|
|
|
ImageButton { |
|
|
|
|
ToolTip.delay: Nheko.tooltipDelay |
|
|
|
|
ToolTip.text: (row.model && row.model.threadId) ? qsTr("Reply in thread") : qsTr("New thread") |
|
|
|
|
ToolTip.visible: hovered |
|
|
|
|
hoverEnabled: true |
|
|
|
|
image: (row.model && row.model.threadId) ? ":/icons/icons/ui/thread.svg" : ":/icons/icons/ui/new-thread.svg" |
|
|
|
|
visible: room ? room.permissions.canSend(MtxEvent.TextMessage) : false |
|
|
|
|
width: 16 |
|
|
|
|
|
|
|
|
|
onClicked: room.thread = (row.model.threadId || row.model.eventId) |
|
|
|
|
} |
|
|
|
|
ImageButton { |
|
|
|
|
ToolTip.delay: Nheko.tooltipDelay |
|
|
|
|
ToolTip.text: qsTr("Reply") |
|
|
|
|
ToolTip.visible: hovered |
|
|
|
|
hoverEnabled: true |
|
|
|
|
image: ":/icons/icons/ui/reply.svg" |
|
|
|
|
visible: room ? room.permissions.canSend(MtxEvent.TextMessage) : false |
|
|
|
|
width: 16 |
|
|
|
|
|
|
|
|
|
onClicked: room.reply = row.model.eventId |
|
|
|
|
} |
|
|
|
|
ImageButton { |
|
|
|
|
ToolTip.delay: Nheko.tooltipDelay |
|
|
|
|
ToolTip.text: qsTr("Go to message") |
|
|
|
|
ToolTip.visible: hovered |
|
|
|
|
buttonTextColor: palette.buttonText |
|
|
|
|
hoverEnabled: true |
|
|
|
|
image: ":/icons/icons/ui/go-to.svg" |
|
|
|
|
visible: !!row.model && filteredTimeline.filterByContent |
|
|
|
|
width: 16 |
|
|
|
|
|
|
|
|
|
onClicked: { |
|
|
|
|
topBar.searchString = ""; |
|
|
|
|
room.showEvent(row.model.eventId); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
ImageButton { |
|
|
|
|
id: optionsButton |
|
|
|
|
ImageButton { |
|
|
|
|
ToolTip.delay: Nheko.tooltipDelay |
|
|
|
|
ToolTip.text: (row.model && row.model.threadId) ? qsTr("Reply in thread") : qsTr("New thread") |
|
|
|
|
ToolTip.visible: hovered |
|
|
|
|
hoverEnabled: true |
|
|
|
|
image: (row.model && row.model.threadId) ? ":/icons/icons/ui/thread.svg" : ":/icons/icons/ui/new-thread.svg" |
|
|
|
|
visible: room ? room.permissions.canSend(MtxEvent.TextMessage) : false |
|
|
|
|
width: 16 |
|
|
|
|
|
|
|
|
|
onClicked: room.thread = (row.model.threadId || row.model.eventId) |
|
|
|
|
} |
|
|
|
|
ImageButton { |
|
|
|
|
ToolTip.delay: Nheko.tooltipDelay |
|
|
|
|
ToolTip.text: qsTr("Reply") |
|
|
|
|
ToolTip.visible: hovered |
|
|
|
|
hoverEnabled: true |
|
|
|
|
image: ":/icons/icons/ui/reply.svg" |
|
|
|
|
visible: room ? room.permissions.canSend(MtxEvent.TextMessage) : false |
|
|
|
|
width: 16 |
|
|
|
|
|
|
|
|
|
onClicked: room.reply = row.model.eventId |
|
|
|
|
} |
|
|
|
|
ImageButton { |
|
|
|
|
ToolTip.delay: Nheko.tooltipDelay |
|
|
|
|
ToolTip.text: qsTr("Go to message") |
|
|
|
|
ToolTip.visible: hovered |
|
|
|
|
buttonTextColor: palette.buttonText |
|
|
|
|
hoverEnabled: true |
|
|
|
|
image: ":/icons/icons/ui/go-to.svg" |
|
|
|
|
visible: !!row.model && filteredTimeline.filterByContent |
|
|
|
|
width: 16 |
|
|
|
|
|
|
|
|
|
onClicked: { |
|
|
|
|
topBar.searchString = ""; |
|
|
|
|
room.showEvent(row.model.eventId); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
ImageButton { |
|
|
|
|
id: optionsButton |
|
|
|
|
|
|
|
|
|
ToolTip.delay: Nheko.tooltipDelay |
|
|
|
|
ToolTip.text: qsTr("Options") |
|
|
|
|
ToolTip.visible: hovered |
|
|
|
|
hoverEnabled: true |
|
|
|
|
image: ":/icons/icons/ui/options.svg" |
|
|
|
|
width: 16 |
|
|
|
|
ToolTip.delay: Nheko.tooltipDelay |
|
|
|
|
ToolTip.text: qsTr("Options") |
|
|
|
|
ToolTip.visible: hovered |
|
|
|
|
hoverEnabled: true |
|
|
|
|
image: ":/icons/icons/ui/options.svg" |
|
|
|
|
width: 16 |
|
|
|
|
|
|
|
|
|
onClicked: messageContextMenuC.show(row.model.eventId, row.model.threadId, row.model.type, row.model.isSender, row.model.isEncrypted, row.model.isEditable, "", row.model.body, optionsButton) |
|
|
|
|
onClicked: messageContextMenuC.show(row.model.eventId, row.model.threadId, row.model.type, row.model.isSender, row.model.isEncrypted, row.model.isEditable, "", row.model.body, optionsButton) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
Shortcut { |
|
|
|
|
sequence: StandardKey.MoveToPreviousPage |
|
|
|
|
Shortcut { |
|
|
|
|
sequence: StandardKey.MoveToPreviousPage |
|
|
|
|
|
|
|
|
|
onActivated: { |
|
|
|
|
chat.contentY = chat.contentY - chat.height * 0.9; |
|
|
|
|
chat.returnToBounds(); |
|
|
|
|
onActivated: { |
|
|
|
|
chat.contentY = chat.contentY - chat.height * 0.9; |
|
|
|
|
chat.returnToBounds(); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
Shortcut { |
|
|
|
|
sequence: StandardKey.MoveToNextPage |
|
|
|
|
Shortcut { |
|
|
|
|
sequence: StandardKey.MoveToNextPage |
|
|
|
|
|
|
|
|
|
onActivated: { |
|
|
|
|
chat.contentY = chat.contentY + chat.height * 0.9; |
|
|
|
|
chat.returnToBounds(); |
|
|
|
|
onActivated: { |
|
|
|
|
chat.contentY = chat.contentY + chat.height * 0.9; |
|
|
|
|
chat.returnToBounds(); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
Shortcut { |
|
|
|
|
sequence: StandardKey.Cancel |
|
|
|
|
Shortcut { |
|
|
|
|
sequence: StandardKey.Cancel |
|
|
|
|
|
|
|
|
|
onActivated: { |
|
|
|
|
if (room.input.uploads.length > 0) |
|
|
|
|
onActivated: { |
|
|
|
|
if (room.input.uploads.length > 0) |
|
|
|
|
room.input.declineUploads(); |
|
|
|
|
else if (room.reply) |
|
|
|
|
else if (room.reply) |
|
|
|
|
room.reply = undefined; |
|
|
|
|
else if (room.edit) |
|
|
|
|
else if (room.edit) |
|
|
|
|
room.edit = undefined; |
|
|
|
|
else |
|
|
|
|
else |
|
|
|
|
room.thread = undefined; |
|
|
|
|
TimelineManager.focusMessageInput(); |
|
|
|
|
TimelineManager.focusMessageInput(); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// These shortcuts use the room timeline because switching to threads and out is annoying otherwise. |
|
|
|
|
// Better solution welcome. |
|
|
|
|
Shortcut { |
|
|
|
|
sequence: "Alt+Up" |
|
|
|
|
// These shortcuts use the room timeline because switching to threads and out is annoying otherwise. |
|
|
|
|
// Better solution welcome. |
|
|
|
|
Shortcut { |
|
|
|
|
sequence: "Alt+Up" |
|
|
|
|
|
|
|
|
|
onActivated: room.reply = room.indexToId(room.reply ? room.idToIndex(room.reply) + 1 : 0) |
|
|
|
|
} |
|
|
|
|
Shortcut { |
|
|
|
|
sequence: "Alt+Down" |
|
|
|
|
onActivated: room.reply = room.indexToId(room.reply ? room.idToIndex(room.reply) + 1 : 0) |
|
|
|
|
} |
|
|
|
|
Shortcut { |
|
|
|
|
sequence: "Alt+Down" |
|
|
|
|
|
|
|
|
|
onActivated: { |
|
|
|
|
var idx = room.reply ? room.idToIndex(room.reply) - 1 : -1; |
|
|
|
|
room.reply = idx >= 0 ? room.indexToId(idx) : null; |
|
|
|
|
onActivated: { |
|
|
|
|
var idx = room.reply ? room.idToIndex(room.reply) - 1 : -1; |
|
|
|
|
room.reply = idx >= 0 ? room.indexToId(idx) : null; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
Shortcut { |
|
|
|
|
sequence: "Alt+F" |
|
|
|
|
|
|
|
|
|
onActivated: { |
|
|
|
|
if (room.reply) { |
|
|
|
|
var forwardMess = forwardCompleterComponent.createObject(timelineRoot); |
|
|
|
|
forwardMess.setMessageEventId(room.reply); |
|
|
|
|
forwardMess.open(); |
|
|
|
|
room.reply = null; |
|
|
|
|
timelineRoot.destroyOnClose(forwardMess); |
|
|
|
|
Shortcut { |
|
|
|
|
sequence: "Alt+F" |
|
|
|
|
|
|
|
|
|
onActivated: { |
|
|
|
|
if (room.reply) { |
|
|
|
|
var forwardMess = forwardCompleterComponent.createObject(timelineRoot); |
|
|
|
|
forwardMess.setMessageEventId(room.reply); |
|
|
|
|
forwardMess.open(); |
|
|
|
|
room.reply = null; |
|
|
|
|
timelineRoot.destroyOnClose(forwardMess); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
Shortcut { |
|
|
|
|
sequence: "Ctrl+E" |
|
|
|
|
Shortcut { |
|
|
|
|
sequence: "Ctrl+E" |
|
|
|
|
|
|
|
|
|
onActivated: { |
|
|
|
|
room.edit = room.reply; |
|
|
|
|
onActivated: { |
|
|
|
|
room.edit = room.reply; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
Timer { |
|
|
|
|
id: readTimer |
|
|
|
|
Timer { |
|
|
|
|
id: readTimer |
|
|
|
|
|
|
|
|
|
interval: 1000 |
|
|
|
|
interval: 1000 |
|
|
|
|
|
|
|
|
|
// force current read index to update |
|
|
|
|
onTriggered: { |
|
|
|
|
if (room) |
|
|
|
|
// force current read index to update |
|
|
|
|
onTriggered: { |
|
|
|
|
if (room) |
|
|
|
|
room.setCurrentIndex(room.currentIndex); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
Platform.Menu { |
|
|
|
|
id: messageContextMenuC |
|
|
|
|
|
|
|
|
@ -641,7 +636,7 @@ Item { |
|
|
|
|
anchors { |
|
|
|
|
bottom: parent.bottom |
|
|
|
|
bottomMargin: Nheko.paddingMedium + (fullWidth - width) / 2 |
|
|
|
|
right: scrollbar.left |
|
|
|
|
right: parent.left |
|
|
|
|
rightMargin: Nheko.paddingMedium + (fullWidth - width) / 2 |
|
|
|
|
} |
|
|
|
|
Image { |
|
|
|
|