web/composer: send typing notifications

This commit is contained in:
Tulir Asokan 2024-10-15 00:40:06 +03:00
parent c16a2c2c80
commit 3c596a200f
4 changed files with 27 additions and 5 deletions

2
go.mod
View file

@ -13,7 +13,7 @@ require (
golang.org/x/crypto v0.27.0 golang.org/x/crypto v0.27.0
gopkg.in/yaml.v3 v3.0.1 gopkg.in/yaml.v3 v3.0.1
maunium.net/go/mauflag v1.0.0 maunium.net/go/mauflag v1.0.0
maunium.net/go/mautrix v0.21.1-0.20241014142315-efc532bfb2ce maunium.net/go/mautrix v0.21.1-0.20241014212413-e2c698098862
) )
require ( require (

4
go.sum
View file

@ -66,5 +66,5 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
maunium.net/go/mauflag v1.0.0 h1:YiaRc0tEI3toYtJMRIfjP+jklH45uDHtT80nUamyD4M= maunium.net/go/mauflag v1.0.0 h1:YiaRc0tEI3toYtJMRIfjP+jklH45uDHtT80nUamyD4M=
maunium.net/go/mauflag v1.0.0/go.mod h1:nLivPOpTpHnpzEh8jEdSL9UqO9+/KBJFmNRlwKfkPeA= maunium.net/go/mauflag v1.0.0/go.mod h1:nLivPOpTpHnpzEh8jEdSL9UqO9+/KBJFmNRlwKfkPeA=
maunium.net/go/mautrix v0.21.1-0.20241014142315-efc532bfb2ce h1:7fgaFJvpsy11S8KcZhbcwhg4kPzFid9aRNgqJjNpOHE= maunium.net/go/mautrix v0.21.1-0.20241014212413-e2c698098862 h1:L+UmRjYbb7Y1R/iOpVCFksH9oGI59o8qI07S3hy+hq4=
maunium.net/go/mautrix v0.21.1-0.20241014142315-efc532bfb2ce/go.mod h1:yIs8uVcl3ZiTuDzAYmk/B4/z9dQqegF0rcOWV4ncgko= maunium.net/go/mautrix v0.21.1-0.20241014212413-e2c698098862/go.mod h1:yIs8uVcl3ZiTuDzAYmk/B4/z9dQqegF0rcOWV4ncgko=

View file

@ -131,6 +131,10 @@ export default abstract class RPCClient {
return this.request("mark_read", { room_id, event_id, receipt_type }) return this.request("mark_read", { room_id, event_id, receipt_type })
} }
setTyping(room_id: RoomID, timeout: number): Promise<boolean> {
return this.request("set_typing", { room_id, timeout })
}
ensureGroupSessionShared(room_id: RoomID): Promise<boolean> { ensureGroupSessionShared(room_id: RoomID): Promise<boolean> {
return this.request("ensure_group_session_shared", { room_id }) return this.request("ensure_group_session_shared", { room_id })
} }

View file

@ -37,6 +37,7 @@ const MessageComposer = ({ room, replyTo, setTextRows, closeReply }: MessageComp
const client = use(ClientContext)! const client = use(ClientContext)!
const [text, setText] = useState("") const [text, setText] = useState("")
const textRows = useRef(1) const textRows = useRef(1)
const typingSentAt = useRef(0)
const fullSetText = useCallback((text: string, setDraft: boolean) => { const fullSetText = useCallback((text: string, setDraft: boolean) => {
setText(text) setText(text)
textRows.current = text === "" ? 1 : text.split("\n").length textRows.current = text === "" ? 1 : text.split("\n").length
@ -74,12 +75,29 @@ const MessageComposer = ({ room, replyTo, setTextRows, closeReply }: MessageComp
}, [sendMessage]) }, [sendMessage])
const onChange = useCallback((evt: React.ChangeEvent<HTMLTextAreaElement>) => { const onChange = useCallback((evt: React.ChangeEvent<HTMLTextAreaElement>) => {
fullSetText(evt.target.value, true) fullSetText(evt.target.value, true)
}, [fullSetText]) const now = Date.now()
if (evt.target.value !== "" && typingSentAt.current + 5_000 < now) {
typingSentAt.current = now
client.rpc.setTyping(room.roomID, 10_000)
.catch(err => console.error("Failed to send typing notification:", err))
} else if (evt.target.value == "" && typingSentAt.current > 0) {
typingSentAt.current = 0
client.rpc.setTyping(room.roomID, 0)
.catch(err => console.error("Failed to send stop typing notification:", err))
}
}, [client, room.roomID, fullSetText])
// To ensure the cursor jumps to the end, do this in an effect rather than as the initial value of useState // To ensure the cursor jumps to the end, do this in an effect rather than as the initial value of useState
// To try to avoid the input bar flashing, use useLayoutEffect instead of useEffect // To try to avoid the input bar flashing, use useLayoutEffect instead of useEffect
useLayoutEffect(() => { useLayoutEffect(() => {
fullSetText(draftStore.get(room.roomID), false) fullSetText(draftStore.get(room.roomID), false)
}, [room.roomID, fullSetText]) return () => {
if (typingSentAt.current > 0) {
typingSentAt.current = 0
client.rpc.setTyping(room.roomID, 0)
.catch(err => console.error("Failed to send stop typing notification due to room switch:", err))
}
}
}, [client, room.roomID, fullSetText])
return <div className="message-composer"> return <div className="message-composer">
{replyTo && <ReplyBody room={room} event={replyTo} onClose={closeReply}/>} {replyTo && <ReplyBody room={room} event={replyTo} onClose={closeReply}/>}
<div className="input-area"> <div className="input-area">