moment/src/gui/Pages/Chat/Timeline/EventList.qml
2020-04-03 07:53:36 -04:00

296 lines
8.7 KiB
QML

// SPDX-License-Identifier: LGPL-3.0-or-later
import QtQuick 2.12
import QtQuick.Layouts 1.12
import QtQuick.Window 2.12
import Clipboard 0.1
import "../../.."
import "../../../Base"
import "../../../ShortcutBundles"
Rectangle {
color: theme.chat.eventList.background
property alias eventList: eventList
HShortcut {
sequences: window.settings.keys.unfocusOrDeselectAllMessages
onActivated: {
eventList.currentIndex !== -1 ?
eventList.currentIndex = -1 :
eventList.checked = {}
}
}
HShortcut {
sequences: window.settings.keys.focusPreviousMessage
onActivated: eventList.incrementCurrentIndex()
}
HShortcut {
sequences: window.settings.keys.focusNextMessage
onActivated:
eventList.currentIndex === 0 ?
eventList.currentIndex = -1 :
eventList.decrementCurrentIndex()
}
HShortcut {
active: eventList.currentItem
sequences: window.settings.keys.toggleSelectMessage
onActivated: eventList.toggleCheck(eventList.currentIndex)
}
HShortcut {
active: eventList.currentItem
sequences: window.settings.keys.selectMessagesUntilHere
onActivated:
eventList.checkFromLastToHere(eventList.currentIndex)
}
HShortcut {
sequences: window.settings.keys.removeFocusedOrSelectedMessages
onActivated: utils.makePopup(
"Popups/RedactPopup.qml",
chat,
{
userId: chat.userId,
roomId: chat.roomId,
eventIds:
(events || findLastRemovableDelegate()).map(
ev => ev.event_id,
),
isLast: ! events,
onlyOwnMessageWarning:
! chat.roomInfo.can_redact_all &&
events &&
events.length < eventList.selectedCount
}
)
readonly property var events:
eventList.selectedCount ?
eventList.redactableCheckedEvents :
eventList.currentItem &&
eventList.canRedact(eventList.currentItem.currentModel) ?
[eventList.currentItem.currentModel] :
null
function findLastRemovableDelegate() {
for (let i = 0; i < eventList.model.count && i <= 1000; i++) {
const event = eventList.model.get(i)
if (eventList.canRedact(event) &&
event.sender_id === chat.userId) return [event]
}
return []
}
}
HShortcut {
active: eventList.currentItem
sequences: window.settings.keys.debugFocusedMessage
onActivated:
eventList.currentItem.eventContent.debugConsoleLoader.toggle()
}
HShortcut {
sequences: window.settings.keys.clearRoomMessages
onActivated: utils.makePopup(
"Popups/ClearMessagesPopup.qml",
mainUI,
{
userId: window.uiState.pageProperties.userId,
roomId: window.uiState.pageProperties.roomId,
}
)
}
FlickShortcuts {
active: ! mainUI.debugConsole.visible
flickable: eventList
}
HListView {
id: eventList
clip: true
keyNavigationWraps: false
anchors.fill: parent
anchors.leftMargin: theme.spacing
anchors.rightMargin: theme.spacing
topMargin: theme.spacing
bottomMargin: theme.spacing
verticalLayoutDirection: ListView.BottomToTop
// Keep x scroll pages cached, to limit images having to be
// reloaded from network.
cacheBuffer: Screen.desktopAvailableHeight * 2
model: ModelStore.get(chat.userId, chat.roomId, "events")
delegate: EventDelegate {}
highlight: Rectangle {
color: theme.chat.message.focusedHighlight
opacity: theme.chat.message.focusedHighlightOpacity
}
// Since the list is BottomToTop, this is actually a header
footer: Item {
width: eventList.width
height: (button.height + theme.spacing * 2) * opacity
opacity: eventList.loading ? 1 : 0
visible: opacity > 0
Behavior on opacity { HNumberAnimation {} }
HButton {
id: button
width: Math.min(parent.width, implicitWidth)
anchors.centerIn: parent
loading: true
text: qsTr("Loading previous messages...")
enableRadius: true
iconItem.small: true
}
}
onYPosChanged:
if (canLoad && yPos < 0.1) Qt.callLater(loadPastEvents)
// When an invited room becomes joined, we should now be able to
// fetch past events.
onInviterChanged: canLoad = true
property string inviter: chat.roomInfo.inviter || ""
property real yPos: visibleArea.yPosition
property bool canLoad: true
property bool loading: false
property bool ownEventsOnRight:
width < theme.chat.eventList.ownEventsOnRightUnderWidth
property string delegateWithSelectedText: ""
property string selectedText: ""
readonly property var redactableCheckedEvents:
getSortedChecked().filter(ev => eventList.canRedact(ev))
function copySelectedDelegates() {
if (eventList.selectedText) {
Clipboard.text = eventList.selectedText
return
}
if (! eventList.selectedCount && eventList.currentIndex !== -1) {
const model = eventList.model.get(eventList.currentIndex)
const source = JSON.parse(model.source)
Clipboard.text =
"body" in source ?
source.body :
utils.stripHtmlTags(utils.processedEventText(model))
return
}
const contents = []
for (const model of eventList.getSortedChecked()) {
const source = JSON.parse(model.source)
contents.push(
"body" in source ?
source.body :
utils.stripHtmlTags(utils.processedEventText(model))
)
}
Clipboard.text = contents.join("\n\n")
}
function canRedact(eventModel) {
return eventModel.event_type !== "RedactedEvent" &&
(chat.roomInfo.can_redact_all ||
eventModel.sender_id === chat.userId)
}
function canCombine(item, itemAfter) {
if (! item || ! itemAfter) return false
return Boolean(
! canTalkBreak(item, itemAfter) &&
! canDayBreak(item, itemAfter) &&
item.sender_id === itemAfter.sender_id &&
utils.minutesBetween(item.date, itemAfter.date) <= 5
)
}
function canTalkBreak(item, itemAfter) {
if (! item || ! itemAfter) return false
return Boolean(
! canDayBreak(item, itemAfter) &&
utils.minutesBetween(item.date, itemAfter.date) >= 20
)
}
function canDayBreak(item, itemAfter) {
if (itemAfter && itemAfter.event_type === "RoomCreateEvent")
return true
if (! item || ! itemAfter || ! item.date || ! itemAfter.date)
return false
return item.date.getDate() !== itemAfter.date.getDate()
}
function loadPastEvents() {
// try/catch blocks to hide pyotherside error when the
// component is destroyed but func is still running
try {
eventList.canLoad = false
eventList.loading = true
py.callClientCoro(
chat.userId,
"load_past_events",
[chat.roomId],
moreToLoad => {
try {
eventList.canLoad = moreToLoad
// Call yPosChanged() to run this func again
// if the loaded messages aren't enough to fill
// the screen.
if (moreToLoad) yPosChanged()
eventList.loading = false
} catch (err) {
return
}
}
)
} catch (err) {
return
}
}
}
HNoticePage {
text: qsTr("No messages to show yet")
visible: eventList.model.count < 1
anchors.fill: parent
}
}