Don't also Aopen the composer's context menu when there are highlighted or selected messages in the timeline and user presses the menu key.
223 lines
6.2 KiB
QML
223 lines
6.2 KiB
QML
// SPDX-License-Identifier: LGPL-3.0-or-later
|
|
|
|
import QtQuick 2.12
|
|
import Clipboard 0.1
|
|
import "../../.."
|
|
import "../../../Base"
|
|
|
|
HTextArea {
|
|
id: textArea
|
|
|
|
property HListView eventList
|
|
|
|
property string indent: " "
|
|
|
|
property string toSend: ""
|
|
property bool textChangedSinceLostFocus: false
|
|
property string writingUserId: chat.userId
|
|
|
|
readonly property QtObject writingUserInfo:
|
|
ModelStore.get("accounts").find(writingUserId)
|
|
|
|
readonly property int cursorY:
|
|
textArea.text.substring(0, cursorPosition).split("\n").length - 1
|
|
|
|
readonly property int cursorX:
|
|
cursorPosition - lines.slice(0, cursorY).join("").length - cursorY
|
|
|
|
readonly property var lines: textArea.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
|
|
|
|
readonly property var usableAliases: {
|
|
const obj = {}
|
|
|
|
// Get accounts that are members of this room with permission to talk
|
|
for (const [id, alia] of Object.entries(window.settings.writeAliases)){
|
|
const room = ModelStore.get(id, "rooms").find(chat.roomId)
|
|
if (room &&
|
|
! room.inviter_id && ! room.left && room.can_send_messages)
|
|
obj[id] = alia
|
|
}
|
|
|
|
return obj
|
|
}
|
|
|
|
function setTyping(typing) {
|
|
py.callClientCoro(
|
|
writingUserId, "room_typing", [chat.roomId, typing, 5000],
|
|
)
|
|
}
|
|
|
|
function clearReplyTo() {
|
|
if (! chat.replyToEventId) return
|
|
|
|
chat.replyToEventId = ""
|
|
chat.replyToUserId = ""
|
|
chat.replyToDisplayName = ""
|
|
}
|
|
|
|
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)
|
|
textArea.insertAtCursor("\n" + add)
|
|
}
|
|
|
|
function sendText() {
|
|
if (! toSend) return
|
|
|
|
const args = [chat.roomId, toSend, chat.replyToEventId]
|
|
py.callClientCoro(writingUserId, "send_text", args)
|
|
|
|
textArea.clear()
|
|
clearReplyTo()
|
|
}
|
|
|
|
|
|
saveName: "composer"
|
|
saveId: [chat.roomId, writingUserId]
|
|
|
|
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"
|
|
focusedBorderColor: "transparent"
|
|
tabStopDistance: 4 * 4 // 4 spaces
|
|
focus: true
|
|
|
|
// TODO: make this more declarative
|
|
onTextChanged: {
|
|
if (! text || utils.isEmptyObject(usableAliases)) {
|
|
writingUserId = Qt.binding(() => chat.userId)
|
|
toSend = text
|
|
setTyping(Boolean(text))
|
|
textChangedSinceLostFocus = true
|
|
return
|
|
}
|
|
|
|
let foundAlias = null
|
|
|
|
for (const [user, writing_alias] of Object.entries(usableAliases)) {
|
|
if (text.startsWith(writing_alias + " ")) {
|
|
writingUserId = user
|
|
foundAlias = new RegExp("^" + writing_alias + " ")
|
|
break
|
|
}
|
|
}
|
|
|
|
if (foundAlias) {
|
|
toSend = text.replace(foundAlias, "")
|
|
setTyping(Boolean(text))
|
|
textChangedSinceLostFocus = true
|
|
return
|
|
}
|
|
|
|
writingUserId = Qt.binding(() => chat.userId)
|
|
toSend = text
|
|
|
|
const vals = Object.values(usableAliases)
|
|
|
|
const longestAlias =
|
|
vals.reduce((a, b) => a.length > b.length ? a: b)
|
|
|
|
const textNotStartsWithAnyAlias =
|
|
! vals.some(a => a.startsWith(text))
|
|
|
|
const textContainsCharNotInAnyAlias =
|
|
vals.every(a => text.split("").some(c => ! a.includes(c)))
|
|
|
|
// Only set typing when it's sure that the user will not use
|
|
// an alias and has written something
|
|
if (toSend &&
|
|
(text.length > longestAlias.length ||
|
|
textNotStartsWithAnyAlias ||
|
|
textContainsCharNotInAnyAlias))
|
|
{
|
|
setTyping(Boolean(text))
|
|
textChangedSinceLostFocus = true
|
|
}
|
|
}
|
|
|
|
onEditingFinished: { // when focus is lost
|
|
if (text && textChangedSinceLostFocus) {
|
|
setTyping(false)
|
|
textChangedSinceLostFocus = false
|
|
}
|
|
}
|
|
|
|
onCustomImagePaste: utils.makePopup(
|
|
"Popups/ConfirmClipboardUploadPopup.qml",
|
|
{
|
|
userId: chat.userId,
|
|
roomId: chat.roomId,
|
|
roomName: chat.roomInfo.display_name,
|
|
},
|
|
)
|
|
|
|
Keys.onEscapePressed: clearReplyTo()
|
|
|
|
Keys.onReturnPressed: ev => {
|
|
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 (eventList && eventList.currentItem)
|
|
eventList.currentItem.openContextMenu()
|
|
}
|
|
|
|
Keys.onTabPressed: ev => {
|
|
ev.accepted = true
|
|
textArea.insertAtCursor(indent)
|
|
}
|
|
|
|
Keys.onPressed: ev => {
|
|
if (ev.matches(StandardKey.Copy) &&
|
|
! textArea.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 &&
|
|
// ! textArea.selectedText)
|
|
// {
|
|
// ev.accepted = true
|
|
// textArea.remove(
|
|
// cursorPosition - deleteCharsOnBackspace,
|
|
// cursorPosition
|
|
// )
|
|
// }
|
|
}
|
|
}
|