moment/src/gui/Pages/Chat/Composer/MessageArea.qml

249 lines
6.9 KiB
QML
Raw Normal View History

// SPDX-License-Identifier: LGPL-3.0-or-later
import QtQuick 2.12
import Clipboard 0.1
import "../../.."
import "../../../Base"
HTextArea {
2020-08-20 12:21:47 -04:00
id: area
property HListView eventList
2020-08-21 01:17:29 -04:00
property bool autoCompletionOpen: false
property var usersCompleted: ({})
property string indent: " "
property string userSetAsTyping: ""
property bool textChangedSinceLostFocus: false
readonly property var usableAliases: {
const obj = {}
const aliases = window.settings.writeAliases
// Get accounts that are members of this room with permission to talk
for (const [id, alias] of Object.entries(aliases)) {
const room = ModelStore.get(id, "rooms").find(chat.roomId)
room && ! room.inviter_id && ! room.left && room.can_send_messages?
obj[id] = alias.trim().split(/\s/)[0] :
null
}
return obj
}
readonly property var candidateAliases: {
if (! text) return []
const candidates = []
const words = text.split(" ")
for (const [userId, alias] of Object.entries(usableAliases))
if ((words.length === 1 && alias.startsWith(words[0])) ||
(words.length > 1 && words[0] == alias))
candidates.push({id: userId, text: alias})
return candidates
}
readonly property var usingAlias:
candidateAliases.length === 1 && text.includes(" ") ?
candidateAliases[0] :
null
readonly property string writerId: usingAlias ? usingAlias.id : chat.userId
readonly property string toSend:
usingAlias ? text.replace(usingAlias.text + " ", "") : text
readonly property int cursorY:
text.substring(0, cursorPosition).split("\n").length - 1
readonly property int cursorX:
cursorPosition - lines.slice(0, cursorY).join("").length - cursorY
readonly property var lines: text.split("\n")
readonly property string lineText: lines[cursorY] || ""
readonly property string lineTextUntilCursor:
lineText.substring(0, cursorX)
// readonly property int deleteCharsOnBackspace:
// lineTextUntilCursor.match(/^ +$/) ?
// lineTextUntilCursor.match(/ {1,4}/g).slice(-1)[0].length :
// 1
2020-08-20 12:21:47 -04:00
signal autoCompletePrevious()
signal autoCompleteNext()
signal acceptAutoCompletion()
2020-08-20 12:21:47 -04:00
signal cancelAutoCompletion()
function setTyping(typing) {
2020-08-20 12:21:47 -04:00
if (! area.enabled) return
if (typing && userSetAsTyping && userSetAsTyping !== writerId)
py.callClientCoro(
userSetAsTyping, "room_typing", [chat.roomId, false],
)
const userId = typing ? writerId : userSetAsTyping
userSetAsTyping = typing ? writerId : ""
if (! userId) return // ! typing && ! userSetAsTyping
py.callClientCoro(userId, "room_typing", [chat.roomId, typing])
}
function addNewLine() {
let indents = 0
const parts = lineText.split(indent)
for (const [i, part] of parts.entries()) {
if (i === parts.length - 1 || part) { break }
indents += 1
}
const add = indent.repeat(indents)
2020-08-20 12:21:47 -04:00
area.insertAtCursor("\n" + add)
}
function sendText() {
if (! toSend && ! chat.replyToEventId) return
2020-08-21 01:17:29 -04:00
// Need to copy usersCompleted because the completion UI closing will
// clear it before it reaches Python.
const mentions = Object.assign({}, usersCompleted)
const args = [chat.roomId, toSend, mentions, chat.replyToEventId]
py.callClientCoro(writerId, "send_text", args)
2020-08-20 12:21:47 -04:00
area.clear()
clearReplyTo()
}
saveName: "composer"
saveId: [chat.roomId, chat.userId]
enabled: chat.roomInfo.can_send_messages
disabledText: qsTr("You do not have permission to post in this room")
placeholderText: qsTr("Type a message...")
enableCustomImagePaste: true
menuKeySpawnsMenu:
! (eventList && (eventList.currentItem || eventList.selectedCount))
backgroundColor: "transparent"
2020-06-06 21:54:13 -04:00
focusedBorderColor: "transparent"
tabStopDistance: 4 * 4 // 4 spaces
focus: true
onTextChanged: if (! text) setTyping(false)
onToSendChanged: {
textChangedSinceLostFocus = true
if (toSend && (usingAlias || ! candidateAliases.length)) {
setTyping(true)
}
}
onEditingFinished: { // when focus is lost
if (text && textChangedSinceLostFocus) {
setTyping(false)
textChangedSinceLostFocus = false
}
}
onCustomImagePaste: window.makePopup(
"Popups/ConfirmClipboardUploadPopup.qml",
{
userId: chat.userId,
roomId: chat.roomId,
roomName: chat.roomInfo.display_name,
replyToEventId: chat.replyToEventId,
},
popup => popup.replied.connect(chat.clearReplyTo),
)
2020-08-20 12:21:47 -04:00
Keys.onEscapePressed:
autoCompletionOpen ? cancelAutoCompletion() : clearReplyTo()
Keys.onReturnPressed: ev => {
if (autoCompletionOpen) acceptAutoCompletion()
ev.accepted = true
ev.modifiers & Qt.ShiftModifier ||
ev.modifiers & Qt.ControlModifier ||
ev.modifiers & Qt.AltModifier ?
addNewLine() :
sendText()
}
Keys.onEnterPressed: ev => Keys.returnPressed(ev)
Keys.onMenuPressed: ev => {
if (autoCompletionOpen) acceptAutoCompletion()
2020-08-20 12:21:47 -04:00
if (eventList && eventList.currentItem)
eventList.currentItem.openContextMenu()
}
2020-08-20 12:21:47 -04:00
Keys.onBacktabPressed: ev => {
// if previous char isn't a space/tab/newline
if (text.slice(cursorPosition - 1, cursorPosition).trim()) {
ev.accepted = true
autoCompletePrevious()
}
2020-08-20 12:21:47 -04:00
}
Keys.onTabPressed: ev => {
ev.accepted = true
2020-08-20 12:21:47 -04:00
if (text.slice(cursorPosition - 1, cursorPosition).trim()) {
2020-08-20 12:21:47 -04:00
autoCompleteNext()
return
}
area.insertAtCursor(indent)
}
Keys.onUpPressed: ev => {
ev.accepted = autoCompletionOpen
if (autoCompletionOpen) autoCompletePrevious()
}
Keys.onDownPressed: ev => {
ev.accepted = autoCompletionOpen
if (autoCompletionOpen) autoCompleteNext()
}
Keys.onPressed: ev => {
if (ev.text && autoCompletionOpen) acceptAutoCompletion()
2020-08-20 12:21:47 -04:00
if (ev.matches(StandardKey.Copy) &&
2020-08-20 12:21:47 -04:00
! area.selectedText &&
eventList &&
(eventList.selectedCount || eventList.currentIndex !== -1))
{
ev.accepted = true
eventList.copySelectedDelegates()
return
}
// FIXME: buggy
// if (ev.modifiers === Qt.NoModifier &&
// ev.key === Qt.Key_Backspace &&
2020-08-20 12:21:47 -04:00
// ! area.selectedText)
// {
// ev.accepted = true
2020-08-20 12:21:47 -04:00
// area.remove(
// cursorPosition - deleteCharsOnBackspace,
// cursorPosition
// )
// }
}
}