2020-05-29 16:22:53 -04:00
|
|
|
// SPDX-License-Identifier: LGPL-3.0-or-later
|
|
|
|
|
|
|
|
import QtQuick 2.12
|
2020-07-15 15:10:34 -04:00
|
|
|
import Clipboard 0.1
|
2020-05-29 16:22:53 -04:00
|
|
|
import "../../.."
|
|
|
|
import "../../../Base"
|
|
|
|
|
|
|
|
HTextArea {
|
2020-08-20 12:21:47 -04:00
|
|
|
id: area
|
2020-05-29 16:22:53 -04:00
|
|
|
|
|
|
|
property HListView eventList
|
2020-08-21 01:17:29 -04:00
|
|
|
|
|
|
|
property bool autoCompletionOpen: false
|
|
|
|
property var usersCompleted: ({})
|
2020-05-29 16:22:53 -04:00
|
|
|
|
|
|
|
property string indent: " "
|
|
|
|
|
2020-08-24 05:44:25 -04:00
|
|
|
property string userSetAsTyping: ""
|
2020-05-29 16:22:53 -04:00
|
|
|
property bool textChangedSinceLostFocus: false
|
|
|
|
|
2020-08-24 05:44:25 -04:00
|
|
|
readonly property var usableAliases: {
|
2020-08-24 06:01:09 -04:00
|
|
|
const obj = {}
|
|
|
|
const aliases = window.settings.writeAliases
|
2020-08-24 05:44:25 -04:00
|
|
|
|
|
|
|
// Get accounts that are members of this room with permission to talk
|
2020-08-24 06:01:09 -04:00
|
|
|
for (const [id, alias] of Object.entries(aliases)) {
|
2020-08-24 05:44:25 -04:00
|
|
|
const room = ModelStore.get(id, "rooms").find(chat.roomId)
|
2020-08-24 06:01:09 -04:00
|
|
|
|
|
|
|
room && ! room.inviter_id && ! room.left && room.can_send_messages?
|
|
|
|
obj[id] = alias.trim().split(/\s/)[0] :
|
|
|
|
null
|
2020-08-24 05:44:25 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
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
|
2020-05-29 16:22:53 -04:00
|
|
|
|
|
|
|
readonly property int cursorY:
|
2020-08-21 04:44:55 -04:00
|
|
|
text.substring(0, cursorPosition).split("\n").length - 1
|
2020-05-29 16:22:53 -04:00
|
|
|
|
|
|
|
readonly property int cursorX:
|
|
|
|
cursorPosition - lines.slice(0, cursorY).join("").length - cursorY
|
|
|
|
|
2020-08-21 04:44:55 -04:00
|
|
|
readonly property var lines: text.split("\n")
|
2020-05-29 16:22:53 -04:00
|
|
|
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()
|
2020-08-21 01:46:07 -04:00
|
|
|
signal acceptAutoCompletion()
|
2020-08-20 12:21:47 -04:00
|
|
|
signal cancelAutoCompletion()
|
|
|
|
|
2020-05-29 16:22:53 -04:00
|
|
|
function setTyping(typing) {
|
2020-08-20 12:21:47 -04:00
|
|
|
if (! area.enabled) return
|
2020-07-21 22:13:44 -04:00
|
|
|
|
2020-08-24 05:44:25 -04:00
|
|
|
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])
|
2020-05-29 16:22:53 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
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)
|
2020-05-29 16:22:53 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
function sendText() {
|
2020-08-24 09:38:06 -04:00
|
|
|
if (! toSend && ! chat.replyToEventId) return
|
2020-05-29 16:22:53 -04:00
|
|
|
|
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]
|
2020-08-24 05:44:25 -04:00
|
|
|
py.callClientCoro(writerId, "send_text", args)
|
2020-05-29 16:22:53 -04:00
|
|
|
|
2020-08-20 12:21:47 -04:00
|
|
|
area.clear()
|
2020-05-29 16:22:53 -04:00
|
|
|
clearReplyTo()
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
saveName: "composer"
|
2020-08-23 17:05:30 -04:00
|
|
|
saveId: [chat.roomId, chat.userId]
|
2020-05-29 16:22:53 -04:00
|
|
|
|
|
|
|
enabled: chat.roomInfo.can_send_messages
|
|
|
|
disabledText: qsTr("You do not have permission to post in this room")
|
|
|
|
placeholderText: qsTr("Type a message...")
|
2020-07-15 15:10:34 -04:00
|
|
|
enableCustomImagePaste: true
|
2020-07-20 12:40:38 -04:00
|
|
|
menuKeySpawnsMenu:
|
|
|
|
! (eventList && (eventList.currentItem || eventList.selectedCount))
|
2020-05-29 16:22:53 -04:00
|
|
|
|
|
|
|
backgroundColor: "transparent"
|
2020-06-06 21:54:13 -04:00
|
|
|
focusedBorderColor: "transparent"
|
2020-05-29 16:22:53 -04:00
|
|
|
tabStopDistance: 4 * 4 // 4 spaces
|
|
|
|
focus: true
|
|
|
|
|
2020-08-24 05:44:25 -04:00
|
|
|
onTextChanged: if (! text) setTyping(false)
|
2020-05-29 16:22:53 -04:00
|
|
|
|
2020-08-24 05:44:25 -04:00
|
|
|
onToSendChanged: {
|
|
|
|
textChangedSinceLostFocus = true
|
2020-05-29 16:22:53 -04:00
|
|
|
|
2020-08-24 05:44:25 -04:00
|
|
|
if (toSend && (usingAlias || ! candidateAliases.length)) {
|
|
|
|
setTyping(true)
|
2020-05-29 16:22:53 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
onEditingFinished: { // when focus is lost
|
|
|
|
if (text && textChangedSinceLostFocus) {
|
|
|
|
setTyping(false)
|
|
|
|
textChangedSinceLostFocus = false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-03 01:26:35 -04:00
|
|
|
onCustomImagePaste: window.makePopup(
|
2020-07-16 15:59:54 -04:00
|
|
|
"Popups/ConfirmClipboardUploadPopup.qml",
|
|
|
|
{
|
|
|
|
userId: chat.userId,
|
|
|
|
roomId: chat.roomId,
|
|
|
|
roomName: chat.roomInfo.display_name,
|
2020-08-24 10:17:04 -04:00
|
|
|
replyToEventId: chat.replyToEventId,
|
2020-07-16 15:59:54 -04:00
|
|
|
},
|
2020-08-24 10:17:04 -04:00
|
|
|
popup => popup.replied.connect(chat.clearReplyTo),
|
2020-07-15 15:10:34 -04:00
|
|
|
)
|
|
|
|
|
2020-08-20 12:21:47 -04:00
|
|
|
Keys.onEscapePressed:
|
|
|
|
autoCompletionOpen ? cancelAutoCompletion() : clearReplyTo()
|
2020-05-29 16:22:53 -04:00
|
|
|
|
|
|
|
Keys.onReturnPressed: ev => {
|
2020-08-21 01:46:07 -04:00
|
|
|
if (autoCompletionOpen) acceptAutoCompletion()
|
2020-05-29 16:22:53 -04:00
|
|
|
ev.accepted = true
|
|
|
|
|
|
|
|
ev.modifiers & Qt.ShiftModifier ||
|
|
|
|
ev.modifiers & Qt.ControlModifier ||
|
|
|
|
ev.modifiers & Qt.AltModifier ?
|
|
|
|
addNewLine() :
|
|
|
|
sendText()
|
|
|
|
}
|
|
|
|
|
|
|
|
Keys.onEnterPressed: ev => Keys.returnPressed(ev)
|
|
|
|
|
2020-07-09 22:17:15 -04:00
|
|
|
Keys.onMenuPressed: ev => {
|
2020-08-21 01:46:07 -04:00
|
|
|
if (autoCompletionOpen) acceptAutoCompletion()
|
2020-08-20 12:21:47 -04:00
|
|
|
|
2020-07-09 22:17:15 -04:00
|
|
|
if (eventList && eventList.currentItem)
|
|
|
|
eventList.currentItem.openContextMenu()
|
|
|
|
}
|
|
|
|
|
2020-08-20 12:21:47 -04:00
|
|
|
Keys.onBacktabPressed: ev => {
|
2020-08-21 04:44:55 -04:00
|
|
|
// 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
|
|
|
}
|
|
|
|
|
2020-05-29 16:22:53 -04:00
|
|
|
Keys.onTabPressed: ev => {
|
|
|
|
ev.accepted = true
|
2020-08-20 12:21:47 -04:00
|
|
|
|
2020-08-21 04:44:55 -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()
|
2020-05-29 16:22:53 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
Keys.onPressed: ev => {
|
2020-08-21 01:46:07 -04:00
|
|
|
if (ev.text && autoCompletionOpen) acceptAutoCompletion()
|
2020-08-20 12:21:47 -04:00
|
|
|
|
2020-05-29 16:22:53 -04:00
|
|
|
if (ev.matches(StandardKey.Copy) &&
|
2020-08-20 12:21:47 -04:00
|
|
|
! area.selectedText &&
|
2020-05-29 16:22:53 -04:00
|
|
|
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)
|
2020-05-29 16:22:53 -04:00
|
|
|
// {
|
|
|
|
// ev.accepted = true
|
2020-08-20 12:21:47 -04:00
|
|
|
// area.remove(
|
2020-05-29 16:22:53 -04:00
|
|
|
// cursorPosition - deleteCharsOnBackspace,
|
|
|
|
// cursorPosition
|
|
|
|
// )
|
|
|
|
// }
|
|
|
|
}
|
|
|
|
}
|