mirror of
https://github.com/Nheko-Reborn/nheko.git
synced 2024-10-30 09:30:47 +03:00
Try out scrollview for timeline
This commit is contained in:
parent
fab7805610
commit
280f316b27
6 changed files with 293 additions and 295 deletions
|
@ -143,11 +143,11 @@ Page {
|
||||||
enabled: false
|
enabled: false
|
||||||
height: avatarSize
|
height: avatarSize
|
||||||
roomid: model.id
|
roomid: model.id
|
||||||
textColor: model.avatarUrl.startsWith(":/") ? communityItem.unimportantText : communityItem.importantText
|
textColor: model.avatarUrl?.startsWith(":/") == true ? communityItem.unimportantText : communityItem.importantText
|
||||||
url: {
|
url: {
|
||||||
if (model.avatarUrl.startsWith("mxc://"))
|
if (model.avatarUrl?.startsWith("mxc://") == true)
|
||||||
return model.avatarUrl.replace("mxc://", "image://MxcImage/");
|
return model.avatarUrl.replace("mxc://", "image://MxcImage/");
|
||||||
else if (model.avatarUrl.length > 0)
|
else if ((model.avatarUrl?.length ?? 0) > 0)
|
||||||
return model.avatarUrl;
|
return model.avatarUrl;
|
||||||
else
|
else
|
||||||
return "";
|
return "";
|
||||||
|
|
|
@ -2,9 +2,6 @@
|
||||||
//
|
//
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
// TODO: using any Qt 6 API version will screw up the reply text color. We need to
|
|
||||||
// figure out a more permanent fix than just importing the old version.
|
|
||||||
//import QtQuick 2.15
|
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Controls
|
import QtQuick.Controls
|
||||||
import im.nheko
|
import im.nheko
|
||||||
|
|
|
@ -31,334 +31,329 @@ Item {
|
||||||
|
|
||||||
target: MainWindow
|
target: MainWindow
|
||||||
}
|
}
|
||||||
ScrollBar {
|
|
||||||
id: scrollbar
|
|
||||||
|
|
||||||
anchors.bottom: parent.bottom
|
ScrollView {
|
||||||
anchors.right: parent.right
|
id: scrollView
|
||||||
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
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
anchors.rightMargin: scrollbar.interactive ? scrollbar.width : 0
|
ListView {
|
||||||
// reuseItems still has a few bugs, see https://bugreports.qt.io/browse/QTBUG-95105 https://bugreports.qt.io/browse/QTBUG-95107
|
id: chat
|
||||||
//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 {
|
property int delegateMaxWidth: ((Settings.timelineMaxWidth > 100 && Settings.timelineMaxWidth < chatRoot.availableWidth) ? Settings.timelineMaxWidth : chatRoot.availableWidth) - chatRoot.padding * 2 - scrollView.effectiveScrollBarWidth
|
||||||
id: defaultMessageStyle
|
readonly property alias filteringInProgress: filteredTimeline.filteringInProgress
|
||||||
|
|
||||||
TimelineDefaultMessageStyle {
|
// reuseItems still has a few bugs, see https://bugreports.qt.io/browse/QTBUG-95105 https://bugreports.qt.io/browse/QTBUG-95107
|
||||||
messageActions: messageActionsC
|
//onModelChanged: if (room) room.sendReset()
|
||||||
messageContextMenu: messageContextMenuC
|
//reuseItems: true
|
||||||
replyContextMenu: replyContextMenuC
|
boundsBehavior: Flickable.StopAtBounds
|
||||||
scrolledToThis: eventId === room.scrollTarget && (y + height > chat.y + chat.contentY && y < chat.y + chat.height + chat.contentY)
|
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 {
|
||||||
Component {
|
id: bubbleMessageStyle
|
||||||
id: bubbleMessageStyle
|
|
||||||
|
|
||||||
TimelineBubbleMessageStyle {
|
TimelineBubbleMessageStyle {
|
||||||
messageActions: messageActionsC
|
messageActions: messageActionsC
|
||||||
messageContextMenu: messageContextMenuC
|
messageContextMenu: messageContextMenuC
|
||||||
replyContextMenu: replyContextMenuC
|
replyContextMenu: replyContextMenuC
|
||||||
scrolledToThis: eventId === room.scrollTarget && (y + height > chat.y + chat.contentY && y < chat.y + chat.height + chat.contentY)
|
scrolledToThis: eventId === room.scrollTarget && (y + height > chat.y + chat.contentY && y < chat.y + chat.height + chat.contentY)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
delegate: Settings.bubbles ? bubbleMessageStyle : defaultMessageStyle
|
delegate: Settings.bubbles ? bubbleMessageStyle : defaultMessageStyle
|
||||||
footer: Item {
|
footer: Item {
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
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
|
|
||||||
anchors.margins: Nheko.paddingLarge
|
anchors.margins: Nheko.paddingLarge
|
||||||
foreground: palette.mid
|
// hacky, but works
|
||||||
running: (room && room.paginationInProgress) || chat.filteringInProgress
|
height: loadingSpinner.height + 2 * Nheko.paddingLarge
|
||||||
z: 3
|
visible: (room && room.paginationInProgress) || chat.filteringInProgress
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Window.onActiveChanged: readTimer.running = Window.active
|
Spinner {
|
||||||
onCountChanged: {
|
id: loadingSpinner
|
||||||
// Mark timeline as read
|
|
||||||
if (atYEnd && room)
|
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)
|
||||||
model.currentIndex = 0;
|
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
|
TimelineFilter {
|
||||||
|
id: filteredTimeline
|
||||||
|
|
||||||
spacing: messageActionsC.padding
|
filterByContent: chatRoot.searchString
|
||||||
|
filterByThread: room ? room.thread : ""
|
||||||
|
source: room
|
||||||
|
}
|
||||||
|
Control {
|
||||||
|
id: messageActionsC
|
||||||
|
|
||||||
Repeater {
|
property Item attached: null
|
||||||
model: Settings.recentReactions
|
// use comma to update on scroll
|
||||||
visible: room ? room.permissions.canSend(MtxEvent.Reaction) : false
|
property alias model: row.model
|
||||||
|
|
||||||
delegate: AbstractButton {
|
hoverEnabled: true
|
||||||
id: button
|
padding: Nheko.paddingSmall
|
||||||
|
visible: Settings.buttonsInTimeline && !!attached && (attached.hovered || hovered)
|
||||||
|
z: 10
|
||||||
|
parent: chat.contentItem
|
||||||
|
anchors.bottom: attached?.top
|
||||||
|
anchors.right: attached?.right
|
||||||
|
|
||||||
property color buttonTextColor: palette.buttonText
|
background: Rectangle {
|
||||||
property color highlightColor: palette.highlight
|
border.color: palette.buttonText
|
||||||
required property string modelData
|
border.width: 1
|
||||||
property bool showImage: modelData.startsWith("mxc://")
|
color: palette.window
|
||||||
|
radius: padding
|
||||||
|
}
|
||||||
|
contentItem: RowLayout {
|
||||||
|
id: row
|
||||||
|
|
||||||
//Layout.preferredHeight: fontMetrics.height
|
property var model
|
||||||
Layout.alignment: Qt.AlignBottom
|
|
||||||
focusPolicy: Qt.NoFocus
|
spacing: messageActionsC.padding
|
||||||
height: showImage ? 16 : buttonText.implicitHeight
|
|
||||||
implicitHeight: showImage ? 16 : buttonText.implicitHeight
|
Repeater {
|
||||||
implicitWidth: showImage ? 16 : buttonText.implicitWidth
|
model: Settings.recentReactions
|
||||||
width: showImage ? 16 : buttonText.implicitWidth
|
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: {
|
onClicked: {
|
||||||
room.input.reaction(row.model.eventId, modelData);
|
if (row.model.isEditable)
|
||||||
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)
|
|
||||||
room.edit = row.model.eventId;
|
room.edit = row.model.eventId;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
ImageButton {
|
||||||
ImageButton {
|
id: reactButton
|
||||||
id: reactButton
|
|
||||||
|
|
||||||
ToolTip.delay: Nheko.tooltipDelay
|
ToolTip.delay: Nheko.tooltipDelay
|
||||||
ToolTip.text: qsTr("React")
|
ToolTip.text: qsTr("React")
|
||||||
ToolTip.visible: hovered
|
ToolTip.visible: hovered
|
||||||
hoverEnabled: true
|
hoverEnabled: true
|
||||||
image: ":/icons/icons/ui/smile-add.svg"
|
image: ":/icons/icons/ui/smile-add.svg"
|
||||||
visible: room ? room.permissions.canSend(MtxEvent.Reaction) : false
|
visible: room ? room.permissions.canSend(MtxEvent.Reaction) : false
|
||||||
width: 16
|
width: 16
|
||||||
|
|
||||||
onClicked: emojiPopup.visible ? emojiPopup.close() : emojiPopup.show(reactButton, room.roomId, function (plaintext, markdown) {
|
onClicked: emojiPopup.visible ? emojiPopup.close() : emojiPopup.show(reactButton, room.roomId, function (plaintext, markdown) {
|
||||||
var event_id = row.model ? row.model.eventId : "";
|
var event_id = row.model ? row.model.eventId : "";
|
||||||
room.input.reaction(event_id, plaintext);
|
room.input.reaction(event_id, plaintext);
|
||||||
TimelineManager.focusMessageInput();
|
TimelineManager.focusMessageInput();
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
ImageButton {
|
ImageButton {
|
||||||
ToolTip.delay: Nheko.tooltipDelay
|
ToolTip.delay: Nheko.tooltipDelay
|
||||||
ToolTip.text: (row.model && row.model.threadId) ? qsTr("Reply in thread") : qsTr("New thread")
|
ToolTip.text: (row.model && row.model.threadId) ? qsTr("Reply in thread") : qsTr("New thread")
|
||||||
ToolTip.visible: hovered
|
ToolTip.visible: hovered
|
||||||
hoverEnabled: true
|
hoverEnabled: true
|
||||||
image: (row.model && row.model.threadId) ? ":/icons/icons/ui/thread.svg" : ":/icons/icons/ui/new-thread.svg"
|
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
|
visible: room ? room.permissions.canSend(MtxEvent.TextMessage) : false
|
||||||
width: 16
|
width: 16
|
||||||
|
|
||||||
onClicked: room.thread = (row.model.threadId || row.model.eventId)
|
onClicked: room.thread = (row.model.threadId || row.model.eventId)
|
||||||
}
|
}
|
||||||
ImageButton {
|
ImageButton {
|
||||||
ToolTip.delay: Nheko.tooltipDelay
|
ToolTip.delay: Nheko.tooltipDelay
|
||||||
ToolTip.text: qsTr("Reply")
|
ToolTip.text: qsTr("Reply")
|
||||||
ToolTip.visible: hovered
|
ToolTip.visible: hovered
|
||||||
hoverEnabled: true
|
hoverEnabled: true
|
||||||
image: ":/icons/icons/ui/reply.svg"
|
image: ":/icons/icons/ui/reply.svg"
|
||||||
visible: room ? room.permissions.canSend(MtxEvent.TextMessage) : false
|
visible: room ? room.permissions.canSend(MtxEvent.TextMessage) : false
|
||||||
width: 16
|
width: 16
|
||||||
|
|
||||||
onClicked: room.reply = row.model.eventId
|
onClicked: room.reply = row.model.eventId
|
||||||
}
|
}
|
||||||
ImageButton {
|
ImageButton {
|
||||||
ToolTip.delay: Nheko.tooltipDelay
|
ToolTip.delay: Nheko.tooltipDelay
|
||||||
ToolTip.text: qsTr("Go to message")
|
ToolTip.text: qsTr("Go to message")
|
||||||
ToolTip.visible: hovered
|
ToolTip.visible: hovered
|
||||||
buttonTextColor: palette.buttonText
|
buttonTextColor: palette.buttonText
|
||||||
hoverEnabled: true
|
hoverEnabled: true
|
||||||
image: ":/icons/icons/ui/go-to.svg"
|
image: ":/icons/icons/ui/go-to.svg"
|
||||||
visible: !!row.model && filteredTimeline.filterByContent
|
visible: !!row.model && filteredTimeline.filterByContent
|
||||||
width: 16
|
width: 16
|
||||||
|
|
||||||
onClicked: {
|
onClicked: {
|
||||||
topBar.searchString = "";
|
topBar.searchString = "";
|
||||||
room.showEvent(row.model.eventId);
|
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
|
||||||
|
|
||||||
|
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ImageButton {
|
}
|
||||||
id: optionsButton
|
Shortcut {
|
||||||
|
sequence: StandardKey.MoveToPreviousPage
|
||||||
|
|
||||||
ToolTip.delay: Nheko.tooltipDelay
|
onActivated: {
|
||||||
ToolTip.text: qsTr("Options")
|
chat.contentY = chat.contentY - chat.height * 0.9;
|
||||||
ToolTip.visible: hovered
|
chat.returnToBounds();
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
Shortcut {
|
||||||
Shortcut {
|
sequence: StandardKey.MoveToNextPage
|
||||||
sequence: StandardKey.MoveToPreviousPage
|
|
||||||
|
|
||||||
onActivated: {
|
onActivated: {
|
||||||
chat.contentY = chat.contentY - chat.height * 0.9;
|
chat.contentY = chat.contentY + chat.height * 0.9;
|
||||||
chat.returnToBounds();
|
chat.returnToBounds();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
Shortcut {
|
||||||
Shortcut {
|
sequence: StandardKey.Cancel
|
||||||
sequence: StandardKey.MoveToNextPage
|
|
||||||
|
|
||||||
onActivated: {
|
onActivated: {
|
||||||
chat.contentY = chat.contentY + chat.height * 0.9;
|
if (room.input.uploads.length > 0)
|
||||||
chat.returnToBounds();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Shortcut {
|
|
||||||
sequence: StandardKey.Cancel
|
|
||||||
|
|
||||||
onActivated: {
|
|
||||||
if (room.input.uploads.length > 0)
|
|
||||||
room.input.declineUploads();
|
room.input.declineUploads();
|
||||||
else if (room.reply)
|
else if (room.reply)
|
||||||
room.reply = undefined;
|
room.reply = undefined;
|
||||||
else if (room.edit)
|
else if (room.edit)
|
||||||
room.edit = undefined;
|
room.edit = undefined;
|
||||||
else
|
else
|
||||||
room.thread = undefined;
|
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"
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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"
|
|
||||||
|
|
||||||
onActivated: {
|
// These shortcuts use the room timeline because switching to threads and out is annoying otherwise.
|
||||||
room.edit = room.reply;
|
// Better solution welcome.
|
||||||
|
Shortcut {
|
||||||
|
sequence: "Alt+Up"
|
||||||
|
|
||||||
|
onActivated: room.reply = room.indexToId(room.reply ? room.idToIndex(room.reply) + 1 : 0)
|
||||||
}
|
}
|
||||||
}
|
Shortcut {
|
||||||
Timer {
|
sequence: "Alt+Down"
|
||||||
id: readTimer
|
|
||||||
|
|
||||||
interval: 1000
|
onActivated: {
|
||||||
|
var idx = room.reply ? room.idToIndex(room.reply) - 1 : -1;
|
||||||
|
room.reply = idx >= 0 ? room.indexToId(idx) : null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Shortcut {
|
||||||
|
sequence: "Alt+F"
|
||||||
|
|
||||||
// force current read index to update
|
onActivated: {
|
||||||
onTriggered: {
|
if (room.reply) {
|
||||||
if (room)
|
var forwardMess = forwardCompleterComponent.createObject(timelineRoot);
|
||||||
|
forwardMess.setMessageEventId(room.reply);
|
||||||
|
forwardMess.open();
|
||||||
|
room.reply = null;
|
||||||
|
timelineRoot.destroyOnClose(forwardMess);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Shortcut {
|
||||||
|
sequence: "Ctrl+E"
|
||||||
|
|
||||||
|
onActivated: {
|
||||||
|
room.edit = room.reply;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Timer {
|
||||||
|
id: readTimer
|
||||||
|
|
||||||
|
interval: 1000
|
||||||
|
|
||||||
|
// force current read index to update
|
||||||
|
onTriggered: {
|
||||||
|
if (room)
|
||||||
room.setCurrentIndex(room.currentIndex);
|
room.setCurrentIndex(room.currentIndex);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Platform.Menu {
|
Platform.Menu {
|
||||||
id: messageContextMenuC
|
id: messageContextMenuC
|
||||||
|
|
||||||
|
@ -641,7 +636,7 @@ Item {
|
||||||
anchors {
|
anchors {
|
||||||
bottom: parent.bottom
|
bottom: parent.bottom
|
||||||
bottomMargin: Nheko.paddingMedium + (fullWidth - width) / 2
|
bottomMargin: Nheko.paddingMedium + (fullWidth - width) / 2
|
||||||
right: scrollbar.left
|
right: parent.left
|
||||||
rightMargin: Nheko.paddingMedium + (fullWidth - width) / 2
|
rightMargin: Nheko.paddingMedium + (fullWidth - width) / 2
|
||||||
}
|
}
|
||||||
Image {
|
Image {
|
||||||
|
|
|
@ -29,7 +29,7 @@ Rectangle {
|
||||||
anchors.rightMargin: replyPopup.width < 450 ? 2 * (22 + 16) : 3 * (22 + 16)
|
anchors.rightMargin: replyPopup.width < 450 ? 2 * (22 + 16) : 3 * (22 + 16)
|
||||||
anchors.top: parent.top
|
anchors.top: parent.top
|
||||||
anchors.topMargin: Nheko.paddingSmall
|
anchors.topMargin: Nheko.paddingSmall
|
||||||
eventId: room.reply ?? ""
|
eventId: room?.reply ?? ""
|
||||||
userColor: TimelineManager.userColor(modelData.userId, palette.window)
|
userColor: TimelineManager.userColor(modelData.userId, palette.window)
|
||||||
visible: room && room.reply
|
visible: room && room.reply
|
||||||
maxWidth: parent.width - anchors.leftMargin - anchors.rightMargin
|
maxWidth: parent.width - anchors.leftMargin - anchors.rightMargin
|
||||||
|
|
|
@ -4,10 +4,10 @@
|
||||||
|
|
||||||
import ".."
|
import ".."
|
||||||
import "../components"
|
import "../components"
|
||||||
import QtQuick 2.12
|
import QtQuick
|
||||||
import QtQuick.Controls 2.5
|
import QtQuick.Controls
|
||||||
import QtQuick.Layouts 1.3
|
import QtQuick.Layouts
|
||||||
import im.nheko 1.0
|
import im.nheko
|
||||||
|
|
||||||
|
|
||||||
ApplicationWindow {
|
ApplicationWindow {
|
||||||
|
|
|
@ -163,14 +163,20 @@ TimelineFilter::setSource(TimelineModel *s)
|
||||||
|
|
||||||
this->setSourceModel(s);
|
this->setSourceModel(s);
|
||||||
|
|
||||||
connect(s, &TimelineModel::currentIndexChanged, this, &TimelineFilter::currentIndexChanged);
|
if (s) {
|
||||||
connect(
|
connect(
|
||||||
s, &TimelineModel::fetchedMore, this, &TimelineFilter::fetchAgain, Qt::QueuedConnection);
|
s, &TimelineModel::currentIndexChanged, this, &TimelineFilter::currentIndexChanged);
|
||||||
connect(s,
|
connect(s,
|
||||||
&TimelineModel::dataChanged,
|
&TimelineModel::fetchedMore,
|
||||||
this,
|
this,
|
||||||
&TimelineFilter::sourceDataChanged,
|
&TimelineFilter::fetchAgain,
|
||||||
Qt::QueuedConnection);
|
Qt::QueuedConnection);
|
||||||
|
connect(s,
|
||||||
|
&TimelineModel::dataChanged,
|
||||||
|
this,
|
||||||
|
&TimelineFilter::sourceDataChanged,
|
||||||
|
Qt::QueuedConnection);
|
||||||
|
}
|
||||||
|
|
||||||
// reset the search index a second time just to be safe.
|
// reset the search index a second time just to be safe.
|
||||||
incrementalSearchIndex = 0;
|
incrementalSearchIndex = 0;
|
||||||
|
|
Loading…
Reference in a new issue