summary refs log tree commit diff
path: root/resources/qml/MessageView.qml
diff options
context:
space:
mode:
Diffstat (limited to 'resources/qml/MessageView.qml')
-rw-r--r--resources/qml/MessageView.qml860
1 files changed, 426 insertions, 434 deletions
diff --git a/resources/qml/MessageView.qml b/resources/qml/MessageView.qml
index e8cc9ed8..e801f2d1 100644
--- a/resources/qml/MessageView.qml
+++ b/resources/qml/MessageView.qml
@@ -23,552 +23,544 @@ Item {
 
     ScrollBar {
         id: scrollbar
-        interactive: !touchObserver.wasTouched
         parent: chat.parent
         anchors.top: parent.top
         anchors.right: parent.right
         anchors.bottom: parent.bottom
     }
+    ListView {
+        id: chat
 
-    EventObserver {
-        id: touchObserver
         anchors.fill: 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)
+
+        displayMarginBeginning: height / 2
+        displayMarginEnd: height / 2
+        model: room
+        // 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;
 
-            anchors.fill: parent
-
-            property int delegateMaxWidth: ((Settings.timelineMaxWidth > 100 && Settings.timelineMaxWidth < chatRoot.availableWidth) ? Settings.timelineMaxWidth : chatRoot.availableWidth) - chatRoot.padding * 2 - scrollbar.width
-
-            displayMarginBeginning: height / 2
-            displayMarginEnd: height / 2
-            model: room
-            // 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;
-
-            }
-
-            ScrollBar.vertical: scrollbar
-
-            anchors.rightMargin: scrollbar.interactive ? scrollbar.width : 0
+        }
 
-            Rectangle {
-                //closePolicy: Popup.NoAutoClose
+        ScrollBar.vertical: scrollbar
 
-                id: messageActions
+        anchors.rightMargin: scrollbar.interactive? scrollbar.width : 0
 
-                property Item attached: null
-                property alias model: row.model
-                // use comma to update on scroll
-                property var attachedPos: chat.contentY, attached ? chat.mapFromItem(attached, attached ? attached.width - width : 0, -height) : null
-                readonly property int padding: Nheko.paddingSmall
+        Rectangle {
+            //closePolicy: Popup.NoAutoClose
 
-                visible: Settings.buttonsInTimeline && !!attached && (attached.hovered || messageActionHover.hovered)
-                x: attached ? attachedPos.x : 0
-                y: attached ? attachedPos.y : 0
-                z: 10
-                height: row.implicitHeight + padding * 2
-                width: row.implicitWidth + padding * 2
-                color: Nheko.colors.window
-                border.color: Nheko.colors.buttonText
-                border.width: 1
-                radius: padding
+            id: messageActions
 
-                HoverHandler {
-                    id: messageActionHover
+            property Item attached: null
+            property alias model: row.model
+            // use comma to update on scroll
+            property var attachedPos: chat.contentY, attached ? chat.mapFromItem(attached, attached ? attached.width - width : 0, -height) : null
+            readonly property int padding: Nheko.paddingSmall
 
-                    grabPermissions: PointerHandler.CanTakeOverFromAnything
-                }
+            visible: Settings.buttonsInTimeline && !!attached && (attached.hovered || messageActionHover.hovered)
+            x: attached ? attachedPos.x : 0
+            y: attached ? attachedPos.y : 0
+            z: 10
+            height: row.implicitHeight + padding * 2
+            width: row.implicitWidth + padding * 2
+            color: Nheko.colors.window
+            border.color: Nheko.colors.buttonText
+            border.width: 1
+            radius: padding
 
-                Row {
-                    id: row
+            HoverHandler {
+                id: messageActionHover
 
-                    property var model
+                grabPermissions: PointerHandler.CanTakeOverFromAnything
+            }
 
-                    anchors.centerIn: parent
-                    spacing: messageActions.padding
+            Row {
+                id: row
 
-                    Repeater {
-                        model: Settings.recentReactions
+                property var model
 
-                        delegate: TextButton {
-                            required property string modelData
+                anchors.centerIn: parent
+                spacing: messageActions.padding
 
-                            visible: chat.model ? chat.model.permissions.canSend(MtxEvent.Reaction) : false
+                Repeater {
+                    model: Settings.recentReactions
 
-                            height: fontMetrics.height
-                            font.family: Settings.emojiFont
+                    delegate: TextButton {
+                        required property string modelData
 
-                            text: modelData
-                            onClicked: {
-                                room.input.reaction(row.model.eventId, modelData);
-                                TimelineManager.focusMessageInput();
-                            }
-                        }
-                    }
+                        visible: chat.model ? chat.model.permissions.canSend(MtxEvent.Reaction) : false
 
-                    ImageButton {
-                        id: editButton
+                        height: fontMetrics.height
+                        font.family: Settings.emojiFont
 
-                        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")
+                        text: modelData
                         onClicked: {
-                            if (row.model.isEditable)
-                            chat.model.editAction(row.model.eventId);
-
+                            room.input.reaction(row.model.eventId, modelData);
+                            TimelineManager.focusMessageInput();
                         }
                     }
+                }
 
-                    ImageButton {
-                        id: reactButton
+                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)
+                            chat.model.editAction(row.model.eventId);
 
-                        visible: chat.model ? chat.model.permissions.canSend(MtxEvent.Reaction) : false
-                        width: 16
-                        hoverEnabled: true
-                        image: ":/icons/icons/ui/smile.svg"
-                        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();
-                        })
                     }
+                }
 
-                    ImageButton {
-                        id: replyButton
-
-                        visible: chat.model ? chat.model.permissions.canSend(MtxEvent.TextMessage) : false
-                        width: 16
-                        hoverEnabled: true
-                        image: ":/icons/icons/ui/reply.svg"
-                        ToolTip.visible: hovered
-                        ToolTip.delay: Nheko.tooltipDelay
-                        ToolTip.text: qsTr("Reply")
-                        onClicked: chat.model.replyAction(row.model.eventId)
-                    }
+                ImageButton {
+                    id: reactButton
+
+                    visible: chat.model ? chat.model.permissions.canSend(MtxEvent.Reaction) : false
+                    width: 16
+                    hoverEnabled: true
+                    image: ":/icons/icons/ui/smile.svg"
+                    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();
+                    })
+                }
 
-                    ImageButton {
-                        id: optionsButton
+                ImageButton {
+                    id: replyButton
+
+                    visible: chat.model ? chat.model.permissions.canSend(MtxEvent.TextMessage) : false
+                    width: 16
+                    hoverEnabled: true
+                    image: ":/icons/icons/ui/reply.svg"
+                    ToolTip.visible: hovered
+                    ToolTip.delay: Nheko.tooltipDelay
+                    ToolTip.text: qsTr("Reply")
+                    onClicked: chat.model.replyAction(row.model.eventId)
+                }
 
-                        width: 16
-                        hoverEnabled: true
-                        image: ":/icons/icons/ui/options.svg"
-                        ToolTip.visible: hovered
-                        ToolTip.delay: Nheko.tooltipDelay
-                        ToolTip.text: qsTr("Options")
-                        onClicked: messageContextMenu.show(row.model.eventId, row.model.type, row.model.isSender, row.model.isEncrypted, row.model.isEditable, "", row.model.body, optionsButton)
-                    }
+                ImageButton {
+                    id: optionsButton
 
+                    width: 16
+                    hoverEnabled: true
+                    image: ":/icons/icons/ui/options.svg"
+                    ToolTip.visible: hovered
+                    ToolTip.delay: Nheko.tooltipDelay
+                    ToolTip.text: qsTr("Options")
+                    onClicked: messageContextMenu.show(row.model.eventId, row.model.type, row.model.isSender, row.model.isEncrypted, row.model.isEditable, "", row.model.body, optionsButton)
                 }
 
             }
 
-            ScrollHelper {
-                flickable: parent
-                anchors.fill: parent
-            }
+        }
 
-            Shortcut {
-                sequence: StandardKey.MoveToPreviousPage
-                onActivated: {
-                    chat.contentY = chat.contentY - chat.height / 2;
-                    chat.returnToBounds();
-                }
+        ScrollHelper {
+            flickable: parent
+            anchors.fill: parent
+        }
+
+        Shortcut {
+            sequence: StandardKey.MoveToPreviousPage
+            onActivated: {
+                chat.contentY = chat.contentY - chat.height / 2;
+                chat.returnToBounds();
             }
+        }
 
-            Shortcut {
-                sequence: StandardKey.MoveToNextPage
-                onActivated: {
-                    chat.contentY = chat.contentY + chat.height / 2;
-                    chat.returnToBounds();
-                }
+        Shortcut {
+            sequence: StandardKey.MoveToNextPage
+            onActivated: {
+                chat.contentY = chat.contentY + chat.height / 2;
+                chat.returnToBounds();
             }
+        }
 
-            Shortcut {
-                sequence: StandardKey.Cancel
-                onActivated: {
-                    if (chat.model.reply)
+        Shortcut {
+            sequence: StandardKey.Cancel
+            onActivated: {
+                if (chat.model.reply)
                     chat.model.reply = undefined;
-                    else
+                else
                     chat.model.edit = undefined;
-                }
             }
+        }
 
-            Shortcut {
-                sequence: "Alt+Up"
-                onActivated: chat.model.reply = chat.model.indexToId(chat.model.reply ? chat.model.idToIndex(chat.model.reply) + 1 : 0)
-            }
+        Shortcut {
+            sequence: "Alt+Up"
+            onActivated: chat.model.reply = chat.model.indexToId(chat.model.reply ? chat.model.idToIndex(chat.model.reply) + 1 : 0)
+        }
 
-            Shortcut {
-                sequence: "Alt+Down"
-                onActivated: {
-                    var idx = chat.model.reply ? chat.model.idToIndex(chat.model.reply) - 1 : -1;
-                    chat.model.reply = idx >= 0 ? chat.model.indexToId(idx) : null;
-                }
+        Shortcut {
+            sequence: "Alt+Down"
+            onActivated: {
+                var idx = chat.model.reply ? chat.model.idToIndex(chat.model.reply) - 1 : -1;
+                chat.model.reply = idx >= 0 ? chat.model.indexToId(idx) : null;
             }
+        }
 
-            Shortcut {
-                sequence: "Alt+F"
-                onActivated: {
-                    if (chat.model.reply) {
-                        var forwardMess = forwardCompleterComponent.createObject(timelineRoot);
-                        forwardMess.setMessageEventId(chat.model.reply);
-                        forwardMess.open();
-                        chat.model.reply = null;
-                    }
+        Shortcut {
+            sequence: "Alt+F"
+            onActivated: {
+                if (chat.model.reply) {
+                    var forwardMess = forwardCompleterComponent.createObject(timelineRoot);
+                    forwardMess.setMessageEventId(chat.model.reply);
+                    forwardMess.open();
+                    chat.model.reply = null;
                 }
             }
+        }
 
-            Shortcut {
-                sequence: "Ctrl+E"
-                onActivated: {
-                    chat.model.edit = chat.model.reply;
-                }
+        Shortcut {
+            sequence: "Ctrl+E"
+            onActivated: {
+                chat.model.edit = chat.model.reply;
             }
+        }
 
-            Connections {
-                function onFocusChanged() {
-                    readTimer.running = TimelineManager.isWindowFocused;
-                }
-
-                target: TimelineManager
+        Connections {
+            function onFocusChanged() {
+                readTimer.running = TimelineManager.isWindowFocused;
             }
 
-            Timer {
-                id: readTimer
+            target: TimelineManager
+        }
+
+        Timer {
+            id: readTimer
 
-                // force current read index to update
-                onTriggered: {
-                    if (chat.model)
+            // force current read index to update
+            onTriggered: {
+                if (chat.model)
                     chat.model.setCurrentIndex(chat.model.currentIndex);
 
-                }
-                interval: 1000
             }
+            interval: 1000
+        }
 
-            Component {
-                id: sectionHeader
+        Component {
+            id: sectionHeader
+
+            Column {
+                topPadding: userName_.visible? 4: 0
+                bottomPadding: Settings.bubbles? (isSender? 0 : 2) : 3
+                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
+                    }
 
-                Column {
-                    topPadding: userName_.visible? 4: 0
-                    bottomPadding: Settings.bubbles? (isSender? 0 : 2) : 3
+                }
+
+                Row {
+                    height: userName_.height
                     spacing: 8
-                    visible: (previousMessageUserId !== userId || previousMessageDay !== day || isStateEvent !== previousMessageIsStateEvent)
-                    width: parentWidth
-                    height: ((previousMessageDay !== day) ? dateBubble.height : 0) + (isStateEvent? 0 : userName.height +8 )
+                    visible: !isStateEvent && (!isSender || !Settings.bubbles)
+
+                    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: avatarHover.hovered
+                        ToolTip.delay: Nheko.tooltipDelay
+                        ToolTip.text: userid
 
-                    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
+                        HoverHandler {
+                            id: avatarHover
                         }
 
                     }
 
-                    Row {
-                        height: userName_.height
-                        spacing: 8
-                        visible: !isStateEvent && (!isSender || !Settings.bubbles)
-
-                        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: avatarHover.hovered
-                            ToolTip.delay: Nheko.tooltipDelay
-                            ToolTip.text: userid
-
-                            HoverHandler {
-                                id: avatarHover
-                            }
+                    Connections {
+                        function onRoomAvatarUrlChanged() {
+                            messageUserAvatar.url = chat.model.avatarUrl(userId).replace("mxc://", "image://MxcImage/");
+                        }
 
+                        function onScrollToIndex(index) {
+                            chat.positionViewAtIndex(index, ListView.Center);
                         }
 
-                        Connections {
-                            function onRoomAvatarUrlChanged() {
-                                messageUserAvatar.url = chat.model.avatarUrl(userId).replace("mxc://", "image://MxcImage/");
-                            }
+                        target: chat.model
+                    }
 
-                            function onScrollToIndex(index) {
-                                chat.positionViewAtIndex(index, ListView.Center);
-                            }
+                    Label {
+                        id: userName_
 
-                            target: chat.model
-                        }
+                        text: TimelineManager.escapeEmoji(userName)
+                        color: TimelineManager.userColor(userId, Nheko.colors.base)
+                        textFormat: Text.RichText
+                        ToolTip.visible: displayNameHover.hovered
+                        ToolTip.delay: Nheko.tooltipDelay
+                        ToolTip.text: userId
 
-                        Label {
-                            id: userName_
+                        TapHandler {
+                            onSingleTapped: chat.model.openUserProfile(userId)
+                            dragThreshold: 0
+                        }
 
-                            text: TimelineManager.escapeEmoji(userName)
-                            color: TimelineManager.userColor(userId, Nheko.colors.base)
-                            textFormat: Text.RichText
-                            ToolTip.visible: displayNameHover.hovered
-                            ToolTip.delay: Nheko.tooltipDelay
-                            ToolTip.text: userId
+                        CursorShape {
+                            anchors.fill: parent
+                            cursorShape: Qt.PointingHandCursor
+                        }
 
-                            TapHandler {
-                                onSingleTapped: chat.model.openUserProfile(userId)
-                                dragThreshold: 0
-                            }
+                        HoverHandler {
+                            id: displayNameHover
+                        }
 
-                            CursorShape {
-                                anchors.fill: parent
-                                cursorShape: Qt.PointingHandCursor
-                            }
+                    }
 
-                            HoverHandler {
-                                id: displayNameHover
-                            }
+                    Label {
+                        id: statusMsg
+                        color: Nheko.colors.buttonText
+                        text: Presence.userStatus(userId)
+                        textFormat: Text.PlainText
+                        elide: Text.ElideRight
+                        width: chat.delegateMaxWidth - parent.spacing * 2 - userName.implicitWidth - Nheko.avatarSize
+                        font.italic: true
 
-                        }
+                        Connections {
+                            target: Presence
 
-                        Label {
-                            id: statusMsg
-                            color: Nheko.colors.buttonText
-                            text: Presence.userStatus(userId)
-                            textFormat: Text.PlainText
-                            elide: Text.ElideRight
-                            width: chat.delegateMaxWidth - parent.spacing * 2 - userName.implicitWidth - Nheko.avatarSize
-                            font.italic: true
-
-                            Connections {
-                                target: Presence
-
-                                function onPresenceChanged(id) {
-                                    if (id == userId) statusMsg.text = Presence.userStatus(userId);
-                                }
+                            function onPresenceChanged(id) {
+                                if (id == userId) statusMsg.text = Presence.userStatus(userId);
                             }
                         }
-
                     }
 
                 }
 
             }
 
-            delegate: ItemDelegate {
-                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
-                required property bool isOnlyEmoji
-                required property bool isSender
-                required property bool isEncrypted
-                required property bool isEditable
-                required property bool isEdited
-                required property bool isStateEvent
-                required property bool previousMessageIsStateEvent
-                required property string replyTo
-                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 encryptionError
-                required property var timestamp
-                required property int status
-                required property int index
-                required property int relatedEventCacheBuster
-                required property string previousMessageUserId
-                required property string day
-                required property string previousMessageDay
-                required property string userName
-                property bool scrolledToThis: eventId === chat.model.scrollTarget && (y + height > chat.y + chat.contentY && y < chat.y + chat.height + chat.contentY)
-
-                anchors.horizontalCenter: parent ? parent.horizontalCenter : undefined
-                width: chat.delegateMaxWidth
-                height: section.active ? section.height + timelinerow.height : timelinerow.height
-
-                hoverEnabled: true
-
-                background: Rectangle {
-                    id: scrollHighlight
-
-                    opacity: 0
-                    visible: true
-                    z: 1
-                    enabled: false
-                    color: Nheko.colors.highlight
-
-                    states: State {
-                        name: "revealed"
-                        when: wrapper.scrolledToThis
-                    }
+        }
 
-                    transitions: Transition {
-                        from: ""
-                        to: "revealed"
-
-                        SequentialAnimation {
-                            PropertyAnimation {
-                                target: scrollHighlight
-                                properties: "opacity"
-                                easing.type: Easing.InOutQuad
-                                from: 0
-                                to: 1
-                                duration: 500
-                            }
+        delegate: ItemDelegate {
+            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
+            required property bool isOnlyEmoji
+            required property bool isSender
+            required property bool isEncrypted
+            required property bool isEditable
+            required property bool isEdited
+            required property bool isStateEvent
+            required property bool previousMessageIsStateEvent
+            required property string replyTo
+            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 encryptionError
+            required property var timestamp
+            required property int status
+            required property int index
+            required property int relatedEventCacheBuster
+            required property string previousMessageUserId
+            required property string day
+            required property string previousMessageDay
+            required property string userName
+            property bool scrolledToThis: eventId === chat.model.scrollTarget && (y + height > chat.y + chat.contentY && y < chat.y + chat.height + chat.contentY)
+
+            anchors.horizontalCenter: parent ? parent.horizontalCenter : undefined
+            width: chat.delegateMaxWidth
+            height: section.active ? section.height + timelinerow.height : timelinerow.height
+
+            hoverEnabled: true
+
+            background: Rectangle {
+                id: scrollHighlight
+
+                opacity: 0
+                visible: true
+                z: 1
+                enabled: false
+                color: Nheko.colors.highlight
+
+                states: State {
+                    name: "revealed"
+                    when: wrapper.scrolledToThis
+                }
 
-                            PropertyAnimation {
-                                target: scrollHighlight
-                                properties: "opacity"
-                                easing.type: Easing.InOutQuad
-                                from: 1
-                                to: 0
-                                duration: 500
-                            }
+                transitions: Transition {
+                    from: ""
+                    to: "revealed"
+
+                    SequentialAnimation {
+                        PropertyAnimation {
+                            target: scrollHighlight
+                            properties: "opacity"
+                            easing.type: Easing.InOutQuad
+                            from: 0
+                            to: 1
+                            duration: 500
+                        }
 
-                            ScriptAction {
-                                script: chat.model.eventShown()
-                            }
+                        PropertyAnimation {
+                            target: scrollHighlight
+                            properties: "opacity"
+                            easing.type: Easing.InOutQuad
+                            from: 1
+                            to: 0
+                            duration: 500
+                        }
 
+                        ScriptAction {
+                            script: chat.model.eventShown()
                         }
 
                     }
 
                 }
 
-                Loader {
-                    id: section
-
-                    property int parentWidth: parent.width
-                    property string userId: wrapper.userId
-                    property string previousMessageUserId: wrapper.previousMessageUserId
-                    property string day: wrapper.day
-                    property string previousMessageDay: wrapper.previousMessageDay
-                    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
-                    active: previousMessageUserId !== undefined && previousMessageUserId !== userId || previousMessageDay !== day || previousMessageIsStateEvent !== isStateEvent
-                    //asynchronous: true
-                    sourceComponent: sectionHeader
-                    visible: status == Loader.Ready
-                }
+            }
 
-                TimelineRow {
-                    id: timelinerow
-
-                    hovered: (wrapper.hovered && !messageActionHover.hovered) || (messageActions.model != undefined && messageActions.model.eventId == timelinerow.eventId)
-
-                    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
-                    isOnlyEmoji: wrapper.isOnlyEmoji
-                    isSender: wrapper.isSender
-                    isEncrypted: wrapper.isEncrypted
-                    isEditable: wrapper.isEditable
-                    isEdited: wrapper.isEdited
-                    isStateEvent: wrapper.isStateEvent
-                    replyTo: wrapper.replyTo
-                    userId: wrapper.userId
-                    userName: wrapper.userName
-                    roomTopic: wrapper.roomTopic
-                    roomName: wrapper.roomName
-                    callType: wrapper.callType
-                    reactions: wrapper.reactions
-                    trustlevel: wrapper.trustlevel
-                    encryptionError: wrapper.encryptionError
-                    timestamp: wrapper.timestamp
-                    status: wrapper.status
-                    relatedEventCacheBuster: wrapper.relatedEventCacheBuster
-                    y: section.visible && section.active ? section.y + section.height : 0
-                }
+            Loader {
+                id: section
+
+                property int parentWidth: parent.width
+                property string userId: wrapper.userId
+                property string previousMessageUserId: wrapper.previousMessageUserId
+                property string day: wrapper.day
+                property string previousMessageDay: wrapper.previousMessageDay
+                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
+                active: previousMessageUserId !== undefined && previousMessageUserId !== userId || previousMessageDay !== day || previousMessageIsStateEvent !== isStateEvent
+                //asynchronous: true
+                sourceComponent: sectionHeader
+                visible: status == Loader.Ready
+            }
 
-                onHoveredChanged: {
-                    if (!Settings.mobileMode && hovered) {
-                        if (!messageActionHover.hovered) {
-                            messageActions.attached = timelinerow;
-                            messageActions.model = timelinerow;
-                        }
+            TimelineRow {
+                id: timelinerow
+
+                hovered: (wrapper.hovered && !messageActionHover.hovered) || (messageActions.model != undefined && messageActions.model.eventId == timelinerow.eventId)
+
+                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
+                isOnlyEmoji: wrapper.isOnlyEmoji
+                isSender: wrapper.isSender
+                isEncrypted: wrapper.isEncrypted
+                isEditable: wrapper.isEditable
+                isEdited: wrapper.isEdited
+                isStateEvent: wrapper.isStateEvent
+                replyTo: wrapper.replyTo
+                userId: wrapper.userId
+                userName: wrapper.userName
+                roomTopic: wrapper.roomTopic
+                roomName: wrapper.roomName
+                callType: wrapper.callType
+                reactions: wrapper.reactions
+                trustlevel: wrapper.trustlevel
+                encryptionError: wrapper.encryptionError
+                timestamp: wrapper.timestamp
+                status: wrapper.status
+                relatedEventCacheBuster: wrapper.relatedEventCacheBuster
+                y: section.visible && section.active ? section.y + section.height : 0
+            }
+
+            onHoveredChanged: {
+                if (!Settings.mobileMode && hovered) {
+                    if (!messageActionHover.hovered) {
+                        messageActions.attached = timelinerow;
+                        messageActions.model = timelinerow;
                     }
                 }
+            }
 
-                Connections {
-                    function onMovementEnded() {
-                        if (y + height + 2 * chat.spacing > chat.contentY + chat.height && y < chat.contentY + chat.height)
+            Connections {
+                function onMovementEnded() {
+                    if (y + height + 2 * chat.spacing > chat.contentY + chat.height && y < chat.contentY + chat.height)
                         chat.model.currentIndex = index;
 
-                    }
-
-                    target: chat
                 }
 
+                target: chat
             }
 
-            footer: Item {
-                anchors.horizontalCenter: parent.horizontalCenter
-                anchors.margins: Nheko.paddingLarge
-                visible: chat.model && chat.model.paginationInProgress
-                // hacky, but works
-                height: loadingSpinner.height + 2 * Nheko.paddingLarge
-
-                Spinner {
-                    id: loadingSpinner
-
-                    anchors.centerIn: parent
-                    anchors.margins: Nheko.paddingLarge
-                    running: chat.model && chat.model.paginationInProgress
-                    foreground: Nheko.colors.mid
-                    z: 3
-                }
+        }
 
+        footer: Item {
+            anchors.horizontalCenter: parent.horizontalCenter
+            anchors.margins: Nheko.paddingLarge
+            visible: chat.model && chat.model.paginationInProgress
+            // hacky, but works
+            height: loadingSpinner.height + 2 * Nheko.paddingLarge
+
+            Spinner {
+                id: loadingSpinner
+
+                anchors.centerIn: parent
+                anchors.margins: Nheko.paddingLarge
+                running: chat.model && chat.model.paginationInProgress
+                foreground: Nheko.colors.mid
+                z: 3
             }
 
         }