63af4be1e2
Previously, events for which the sender, target (state_key) or remover was missing from the room members would have their profile fetched from network when registering the event into models. This could cause very slow past events loading times for rooms, since the event registering function (which contained the profile retrieval directives) is run sequentially event-by-event. Missing profiles are now lazy-loaded when events come into the user's view in the QML timeline.
276 lines
7.9 KiB
QML
276 lines
7.9 KiB
QML
// SPDX-License-Identifier: LGPL-3.0-or-later
|
|
|
|
import QtQuick 2.12
|
|
import QtQuick.Layouts 1.12
|
|
import Clipboard 0.1
|
|
import "../../.."
|
|
import "../../../Base"
|
|
|
|
HColumnLayout {
|
|
id: eventDelegate
|
|
width: eventList.width
|
|
|
|
ListView.onRemove: eventList.uncheck(model.id)
|
|
|
|
|
|
enum Media { Page, File, Image, Video, Audio }
|
|
|
|
property var hoveredMediaTypeUrl: []
|
|
|
|
property var fetchProfilesFuture: null
|
|
|
|
// Remember timeline goes from newest message at index 0 to oldest
|
|
readonly property var previousModel: eventList.model.get(model.index + 1)
|
|
readonly property var nextModel: eventList.model.get(model.index - 1)
|
|
readonly property QtObject currentModel: model
|
|
|
|
property bool checked: model.id in eventList.checked
|
|
property bool compact: window.settings.compactMode
|
|
property bool isOwn: chat.userId === model.sender_id
|
|
property bool onRight: ! eventList.ownEventsOnLeft && isOwn
|
|
property bool combine: eventList.canCombine(previousModel, model)
|
|
property bool talkBreak: eventList.canTalkBreak(previousModel, model)
|
|
property bool dayBreak: eventList.canDayBreak(previousModel, model)
|
|
|
|
readonly property bool smallAvatar: compact
|
|
readonly property bool collapseAvatar: combine
|
|
readonly property bool hideAvatar: onRight
|
|
readonly property bool isRedacted: model.event_type === "RedactedEvent"
|
|
|
|
readonly property bool hideNameLine:
|
|
model.event_type === "RoomMessageEmote" ||
|
|
! (
|
|
model.event_type.startsWith("RoomMessage") ||
|
|
model.event_type.startsWith("RoomEncrypted")
|
|
) ||
|
|
onRight ||
|
|
combine
|
|
|
|
readonly property int cursorShape:
|
|
eventContent.hoveredLink || hoveredMediaTypeUrl.length > 0 ?
|
|
Qt.PointingHandCursor :
|
|
|
|
eventContent.hoveredSelectable ? Qt.IBeamCursor :
|
|
|
|
Qt.ArrowCursor
|
|
|
|
readonly property int separationSpacing:
|
|
dayBreak ? theme.spacing * 4 :
|
|
talkBreak ? theme.spacing * 6 :
|
|
combine ? theme.spacing / (compact ? 4 : 2) :
|
|
theme.spacing * (compact ? 1 : 2)
|
|
|
|
readonly property alias eventContent: eventContent
|
|
|
|
// Needed because of eventList's MouseArea which steals the
|
|
// HSelectableLabel's MouseArea hover events
|
|
onCursorShapeChanged: eventList.cursorShape = cursorShape
|
|
|
|
Component.onCompleted: if (model.fetch_profile) py.callClientCoro(
|
|
chat.userId, "get_event_profiles", [chat.roomId, model.id],
|
|
)
|
|
|
|
Component.onDestruction:
|
|
if (fetchProfilesFuture) fetchProfilesFuture.cancel()
|
|
|
|
|
|
function json() {
|
|
let event = ModelStore.get(chat.userId, chat.roomId, "events")
|
|
.get(model.index)
|
|
event = JSON.parse(JSON.stringify(event))
|
|
event.source = JSON.parse(event.source)
|
|
return JSON.stringify(event, null, 4)
|
|
}
|
|
|
|
function openContextMenu() {
|
|
contextMenu.media = eventDelegate.hoveredMediaTypeUrl
|
|
contextMenu.link = eventContent.hoveredLink
|
|
contextMenu.popup()
|
|
}
|
|
|
|
function toggleChecked() {
|
|
eventList.toggleCheck(model.index)
|
|
}
|
|
|
|
|
|
Item {
|
|
Layout.fillWidth: true
|
|
Layout.preferredHeight:
|
|
model.event_type === "RoomCreateEvent" ? 0 : separationSpacing
|
|
}
|
|
|
|
Daybreak {
|
|
visible: dayBreak
|
|
|
|
Layout.fillWidth: true
|
|
Layout.minimumWidth: parent.width
|
|
}
|
|
|
|
Item {
|
|
visible: dayBreak
|
|
|
|
Layout.fillWidth: true
|
|
Layout.preferredHeight: separationSpacing
|
|
}
|
|
|
|
EventContent {
|
|
id: eventContent
|
|
|
|
Layout.fillWidth: true
|
|
|
|
Behavior on x { HNumberAnimation {} }
|
|
}
|
|
|
|
TapHandler {
|
|
acceptedButtons: Qt.LeftButton
|
|
acceptedModifiers: Qt.NoModifier
|
|
onTapped: toggleChecked()
|
|
}
|
|
|
|
TapHandler {
|
|
acceptedButtons: Qt.LeftButton
|
|
acceptedModifiers: Qt.ShiftModifier
|
|
onTapped: eventList.checkFromLastToHere(model.index)
|
|
}
|
|
|
|
TapHandler {
|
|
acceptedButtons: Qt.RightButton
|
|
acceptedPointerTypes: PointerDevice.GenericPointer | PointerDevice.Pen
|
|
onTapped: openContextMenu()
|
|
}
|
|
|
|
TapHandler {
|
|
acceptedPointerTypes: PointerDevice.Finger | PointerDevice.Pen
|
|
onLongPressed: openContextMenu()
|
|
}
|
|
|
|
HMenu {
|
|
id: contextMenu
|
|
|
|
property var media: []
|
|
property string link: ""
|
|
|
|
onClosed: { media = []; link = "" }
|
|
|
|
HMenuItem {
|
|
icon.name: "toggle-select-message"
|
|
text: eventDelegate.checked ? qsTr("Deselect") : qsTr("Select")
|
|
onTriggered: eventDelegate.toggleChecked()
|
|
}
|
|
|
|
HMenuItem {
|
|
visible: eventList.selectedCount >= 2
|
|
icon.name: "deselect-all-messages"
|
|
text: qsTr("Deselect all")
|
|
onTriggered: eventList.checked = {}
|
|
}
|
|
|
|
HMenuItem {
|
|
visible: model.index !== 0
|
|
icon.name: "select-until-here"
|
|
text: qsTr("Select until here")
|
|
onTriggered: eventList.checkFromLastToHere(model.index)
|
|
}
|
|
|
|
HMenuItem {
|
|
id: copyMedia
|
|
icon.name: "copy-link"
|
|
text:
|
|
contextMenu.media.length < 1 ? "" :
|
|
|
|
contextMenu.media[0] === EventDelegate.Media.Page ?
|
|
qsTr("Copy page address") :
|
|
|
|
contextMenu.media[0] === EventDelegate.Media.File ?
|
|
qsTr("Copy file address") :
|
|
|
|
contextMenu.media[0] === EventDelegate.Media.Image ?
|
|
qsTr("Copy image address") :
|
|
|
|
contextMenu.media[0] === EventDelegate.Media.Video ?
|
|
qsTr("Copy video address") :
|
|
|
|
contextMenu.media[0] === EventDelegate.Media.Audio ?
|
|
qsTr("Copy audio address") :
|
|
|
|
qsTr("Copy media address")
|
|
|
|
visible: Boolean(text)
|
|
onTriggered: Clipboard.text = contextMenu.media[1]
|
|
}
|
|
|
|
HMenuItem {
|
|
id: copyLink
|
|
icon.name: "copy-link"
|
|
text: qsTr("Copy link address")
|
|
visible: Boolean(contextMenu.link)
|
|
onTriggered: Clipboard.text = contextMenu.link
|
|
}
|
|
|
|
HMenuItem {
|
|
icon.name: "copy-text"
|
|
text:
|
|
eventList.selectedCount ?
|
|
qsTr("Copy selection") :
|
|
|
|
copyMedia.visible ?
|
|
qsTr("Copy filename") :
|
|
|
|
qsTr("Copy text")
|
|
|
|
onTriggered: {
|
|
if (! eventList.selectedCount && ! eventList.currentItem){
|
|
Clipboard.text = JSON.parse(model.source).body
|
|
return
|
|
}
|
|
|
|
eventList.copySelectedDelegates()
|
|
}
|
|
}
|
|
|
|
HMenuItemPopupSpawner {
|
|
icon.name: "remove-message"
|
|
text: qsTr("Remove")
|
|
enabled: properties.eventSenderAndIds.length
|
|
|
|
popup: "Popups/RedactPopup.qml"
|
|
popupParent: chat
|
|
properties: ({
|
|
preferUserId: chat.userId,
|
|
roomId: chat.roomId,
|
|
eventSenderAndIds: events.map(ev => [ev.sender_id, ev.id]),
|
|
|
|
onlyOwnMessageWarning:
|
|
! chat.roomInfo.can_redact_all &&
|
|
events.length < eventList.selectedCount
|
|
})
|
|
|
|
readonly property var events: {
|
|
eventList.selectedCount ?
|
|
eventList.redactableCheckedEvents :
|
|
|
|
eventList.canRedact(currentModel) ?
|
|
[model] :
|
|
|
|
[]
|
|
}
|
|
}
|
|
|
|
HMenuItem {
|
|
icon.name: "debug"
|
|
text: qsTr("Debug this event")
|
|
onTriggered:
|
|
mainUI.debugConsole.toggle(eventContent, "t.parent.json()")
|
|
}
|
|
|
|
HMenuItemPopupSpawner {
|
|
icon.name: "clear-messages"
|
|
text: qsTr("Clear messages")
|
|
|
|
popup: "Popups/ClearMessagesPopup.qml"
|
|
popupParent: chat
|
|
properties: ({userId: chat.userId, roomId: chat.roomId})
|
|
}
|
|
}
|
|
}
|