moment/src/gui/Pages/Chat/AutoCompletion/UserAutoCompletion.qml

185 lines
5.2 KiB
QML
Raw Normal View History

2020-08-20 12:21:47 -04:00
// SPDX-License-Identifier: LGPL-3.0-or-later
import QtQuick 2.12
import QtQuick.Layouts 1.12
import "../../.."
import "../../../Base"
import "../../../Base/HTile"
HListView {
2020-08-21 01:17:29 -04:00
id: root
2020-08-20 12:21:47 -04:00
property HTextArea textArea
property bool open: false
property var originalWord: null
property int replacementStart: -1
property int replacementEnd: -1
2020-08-20 12:21:47 -04:00
property bool autoOpenCompleted: false
2020-08-21 01:17:29 -04:00
property var usersCompleted: ({}) // {displayName: userId}
2020-08-20 12:21:47 -04:00
readonly property bool autoOpen: {
if (autoOpenCompleted) return true
const current = textArea.getWordBehindCursor()
return current ? /^@.+/.test(current.word) : false
}
readonly property var wordToComplete:
open ? originalWord || textArea.getWordBehindCursor() : null
2020-08-20 12:21:47 -04:00
readonly property string modelFilter:
autoOpen && wordToComplete ? wordToComplete.word.replace(/^@/, "") :
open && wordToComplete ? wordToComplete.word :
2020-08-20 12:21:47 -04:00
""
function getCurrentWordStart() {
const lastWordMatch = /(?:^|\s)[^\s]+$/.exec(textArea.text)
if (! lastWordMatch) return textArea.length
if (! (lastWordMatch.index === 0 && ! textArea.text[0].match(/\s/)))
return lastWordMatch.index + 1
2020-08-20 12:21:47 -04:00
return lastWordMatch.index
}
2020-08-21 01:17:29 -04:00
function replaceCompletionOrCurrentWord(withText) {
const current = textArea.getWordBehindCursor()
if (! current) return
replacementStart === -1 || replacementEnd === -1 ?
textArea.remove(current.start, current.end + 1) :
textArea.remove(replacementStart, replacementEnd)
textArea.insertAtCursor(withText)
2020-08-20 12:21:47 -04:00
}
function previous() {
if (open) {
decrementCurrentIndex()
return
}
open = true
const args = [model.modelId, modelFilter]
2020-08-20 12:21:47 -04:00
py.callCoro("set_string_filter", args, decrementCurrentIndex)
}
function next() {
if (open) {
incrementCurrentIndex()
return
}
open = true
const args = [model.modelId, modelFilter]
2020-08-20 12:21:47 -04:00
py.callCoro("set_string_filter", args, incrementCurrentIndex)
}
2020-08-21 01:17:29 -04:00
function accept() {
if (currentIndex !== -1) {
const member = model.get(currentIndex)
usersCompleted[member.display_name] = member.id
usersCompletedChanged()
}
open = false
}
2020-08-20 12:21:47 -04:00
function cancel() {
if (originalWord) replaceCompletionOrCurrentWord(originalWord.word)
2020-08-20 12:21:47 -04:00
2020-08-21 01:17:29 -04:00
currentIndex = -1
open = false
2020-08-20 12:21:47 -04:00
}
visible: opacity > 0
opacity: open && count ? 1 : 0
2020-08-21 11:14:35 -04:00
bottomMargin: theme.spacing / 2
implicitHeight:
open && count ?
Math.min(window.height, contentHeight + topMargin + bottomMargin) :
0
2020-08-20 12:21:47 -04:00
model: ModelStore.get(chat.userId, chat.roomId, "autocompleted_members")
2020-08-21 09:27:01 -04:00
delegate: CompletableUserDelegate {
2020-08-21 01:17:29 -04:00
width: root.width
colorName: hovered || root.currentIndex === model.index
2020-08-20 12:21:47 -04:00
onClicked: {
2020-08-21 01:17:29 -04:00
currentIndex = model.index
root.open = false
2020-08-20 12:21:47 -04:00
}
}
onAutoOpenChanged: open = autoOpen
onOpenChanged: if (! open) {
originalWord = null
replacementStart = -1
replacementEnd = -1
2020-08-20 12:21:47 -04:00
currentIndex = -1
autoOpenCompleted = false
py.callCoro("set_string_filter", [model.modelId, ""])
}
onModelFilterChanged: {
2020-08-20 12:21:47 -04:00
if (! open) return
py.callCoro("set_string_filter", [model.modelId, modelFilter])
2020-08-20 12:21:47 -04:00
}
onCurrentIndexChanged: {
if (currentIndex === -1) return
if (! originalWord) originalWord = textArea.getWordBehindCursor()
2020-08-20 12:21:47 -04:00
if (autoOpen) autoOpenCompleted = true
const member = model.get(currentIndex)
const replacement = member.display_name || member.id
replaceCompletionOrCurrentWord(replacement)
replacementStart = textArea.cursorPosition - replacement.length
replacementEnd = textArea.cursorPosition
2020-08-20 12:21:47 -04:00
}
Behavior on opacity { HNumberAnimation {} }
Behavior on implicitHeight { HNumberAnimation {} }
2020-08-21 01:17:29 -04:00
2020-08-21 09:27:01 -04:00
Rectangle {
anchors.fill: parent
z: -1
color: theme.chat.userAutoCompletion.background
2020-08-21 09:27:01 -04:00
}
2020-08-21 01:17:29 -04:00
Connections {
target: root.textArea
function onCursorPositionChanged() {
if (! root.open) return
const pos = root.textArea.cursorPosition
const start = root.wordToComplete.start
let end = root.wordToComplete.end + 1
if (currentIndex !== -1) {
const member = model.get(currentIndex)
const repl = member.display_name || member.id
end = root.wordToComplete.start + repl.length
}
if (pos < start || pos > end) root.accept()
}
2020-08-21 01:17:29 -04:00
function onTextChanged() {
let changed = false
for (const displayName of Object.keys(root.usersCompleted)) {
if (! root.textArea.text.includes(displayName)) {
delete root.usersCompleted[displayName]
changed = true
}
}
if (changed) root.usersCompletedChanged()
}
}
2020-08-20 12:21:47 -04:00
}