Refactor MessageArea composer alias handling
Simplify the code, make it more declarative, and improve the reliability of sending typing notifications (notably when changing the used alias in the middle of a message).
This commit is contained in:
parent
aba47ef26b
commit
83f35c034e
2
TODO.md
2
TODO.md
|
@ -1,6 +1,6 @@
|
||||||
# TODO
|
# TODO
|
||||||
|
|
||||||
- model not in list error in the onri test room
|
- session list: prevent tab-focusing the delegates
|
||||||
- refresh server list button
|
- refresh server list button
|
||||||
|
|
||||||
- global presence control
|
- global presence control
|
||||||
|
|
|
@ -1239,8 +1239,8 @@ class MatrixClient(nio.AsyncClient):
|
||||||
):
|
):
|
||||||
"""Set typing notice to the server."""
|
"""Set typing notice to the server."""
|
||||||
|
|
||||||
# Do not send typing notice if the user is invisible
|
|
||||||
presence = self.models["accounts"][self.user_id].presence
|
presence = self.models["accounts"][self.user_id].presence
|
||||||
|
|
||||||
if presence not in [Presence.State.invisible, Presence.State.offline]:
|
if presence not in [Presence.State.invisible, Presence.State.offline]:
|
||||||
await super().room_typing(room_id, typing_state, timeout)
|
await super().room_typing(room_id, typing_state, timeout)
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
import QtQuick 2.12
|
import QtQuick 2.12
|
||||||
import QtQuick.Layouts 1.12
|
import QtQuick.Layouts 1.12
|
||||||
|
import "../../.."
|
||||||
import "../../../Base"
|
import "../../../Base"
|
||||||
import "../AutoCompletion"
|
import "../AutoCompletion"
|
||||||
|
|
||||||
|
@ -24,19 +25,15 @@ Rectangle {
|
||||||
|
|
||||||
HUserAvatar {
|
HUserAvatar {
|
||||||
id: avatar
|
id: avatar
|
||||||
|
|
||||||
|
readonly property QtObject writerInfo:
|
||||||
|
ModelStore.get("accounts").find(clientUserId)
|
||||||
|
|
||||||
|
clientUserId: messageArea.writerId
|
||||||
|
userId: clientUserId
|
||||||
|
mxc: writerInfo ? writerInfo.avatar_url : ""
|
||||||
|
displayName: writerInfo ? writerInfo.display_name : ""
|
||||||
radius: 0
|
radius: 0
|
||||||
clientUserId: messageArea.writingUserId
|
|
||||||
userId: messageArea.writingUserId
|
|
||||||
|
|
||||||
mxc:
|
|
||||||
messageArea.writingUserInfo ?
|
|
||||||
messageArea.writingUserInfo.avatar_url :
|
|
||||||
""
|
|
||||||
|
|
||||||
displayName:
|
|
||||||
messageArea.writingUserInfo ?
|
|
||||||
messageArea.writingUserInfo.display_name :
|
|
||||||
""
|
|
||||||
}
|
}
|
||||||
|
|
||||||
HScrollView {
|
HScrollView {
|
||||||
|
|
|
@ -15,12 +15,47 @@ HTextArea {
|
||||||
|
|
||||||
property string indent: " "
|
property string indent: " "
|
||||||
|
|
||||||
property string toSend: ""
|
property string userSetAsTyping: ""
|
||||||
property bool textChangedSinceLostFocus: false
|
property bool textChangedSinceLostFocus: false
|
||||||
property string writingUserId: chat.userId
|
|
||||||
|
|
||||||
readonly property QtObject writingUserInfo:
|
readonly property var usableAliases: {
|
||||||
ModelStore.get("accounts").find(writingUserId)
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
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:
|
readonly property int cursorY:
|
||||||
text.substring(0, cursorPosition).split("\n").length - 1
|
text.substring(0, cursorPosition).split("\n").length - 1
|
||||||
|
@ -39,20 +74,6 @@ HTextArea {
|
||||||
// lineTextUntilCursor.match(/ {1,4}/g).slice(-1)[0].length :
|
// lineTextUntilCursor.match(/ {1,4}/g).slice(-1)[0].length :
|
||||||
// 1
|
// 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
|
|
||||||
}
|
|
||||||
|
|
||||||
signal autoCompletePrevious()
|
signal autoCompletePrevious()
|
||||||
signal autoCompleteNext()
|
signal autoCompleteNext()
|
||||||
signal acceptAutoCompletion()
|
signal acceptAutoCompletion()
|
||||||
|
@ -61,9 +82,18 @@ HTextArea {
|
||||||
function setTyping(typing) {
|
function setTyping(typing) {
|
||||||
if (! area.enabled) return
|
if (! area.enabled) return
|
||||||
|
|
||||||
|
if (typing && userSetAsTyping && userSetAsTyping !== writerId)
|
||||||
py.callClientCoro(
|
py.callClientCoro(
|
||||||
writingUserId, "room_typing", [chat.roomId, typing, 5000],
|
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 clearReplyTo() {
|
function clearReplyTo() {
|
||||||
|
@ -94,7 +124,7 @@ HTextArea {
|
||||||
// clear it before it reaches Python.
|
// clear it before it reaches Python.
|
||||||
const mentions = Object.assign({}, usersCompleted)
|
const mentions = Object.assign({}, usersCompleted)
|
||||||
const args = [chat.roomId, toSend, mentions, chat.replyToEventId]
|
const args = [chat.roomId, toSend, mentions, chat.replyToEventId]
|
||||||
py.callClientCoro(writingUserId, "send_text", args)
|
py.callClientCoro(writerId, "send_text", args)
|
||||||
|
|
||||||
area.clear()
|
area.clear()
|
||||||
clearReplyTo()
|
clearReplyTo()
|
||||||
|
@ -116,56 +146,13 @@ HTextArea {
|
||||||
tabStopDistance: 4 * 4 // 4 spaces
|
tabStopDistance: 4 * 4 // 4 spaces
|
||||||
focus: true
|
focus: true
|
||||||
|
|
||||||
// TODO: make this more declarative
|
onTextChanged: if (! text) setTyping(false)
|
||||||
onTextChanged: {
|
|
||||||
if (! text || utils.isEmptyObject(usableAliases)) {
|
onToSendChanged: {
|
||||||
writingUserId = Qt.binding(() => chat.userId)
|
|
||||||
toSend = text
|
|
||||||
setTyping(Boolean(text))
|
|
||||||
textChangedSinceLostFocus = true
|
textChangedSinceLostFocus = true
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
let foundAlias = null
|
if (toSend && (usingAlias || ! candidateAliases.length)) {
|
||||||
|
setTyping(true)
|
||||||
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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user