feat(web): per-chat message drafts
Some checks failed
CI / test (push) Failing after 17s

- store unsent draft text per chat in zustand
- restore draft when switching chats
- clear draft after successful send
This commit is contained in:
2026-03-08 02:15:13 +03:00
parent 62390a1727
commit eddd4bda0b
2 changed files with 43 additions and 2 deletions

View File

@@ -7,6 +7,9 @@ import { buildWsUrl } from "../utils/ws";
export function MessageComposer() {
const activeChatId = useChatStore((s) => s.activeChatId);
const me = useAuthStore((s) => s.me);
const draftsByChat = useChatStore((s) => s.draftsByChat);
const setDraft = useChatStore((s) => s.setDraft);
const clearDraft = useChatStore((s) => s.clearDraft);
const addOptimisticMessage = useChatStore((s) => s.addOptimisticMessage);
const confirmMessageByClientId = useChatStore((s) => s.confirmMessageByClientId);
const removeOptimisticMessage = useChatStore((s) => s.removeOptimisticMessage);
@@ -25,6 +28,19 @@ export function MessageComposer() {
const [previewUrl, setPreviewUrl] = useState<string | null>(null);
const [isRecording, setIsRecording] = useState(false);
useEffect(() => {
if (!activeChatId) {
if (text !== "") {
setText("");
}
return;
}
const draft = draftsByChat[activeChatId] ?? "";
if (draft !== text) {
setText(draft);
}
}, [activeChatId, draftsByChat, text]);
useEffect(() => {
return () => {
if (previewUrl) {
@@ -64,6 +80,7 @@ export function MessageComposer() {
const message = await sendMessageWithClientId(activeChatId, textValue, "text", clientMessageId, replyToMessageId);
confirmMessageByClientId(activeChatId, clientMessageId, message);
setText("");
clearDraft(activeChatId);
setReplyToMessage(activeChatId, null);
const ws = getWs();
ws?.send(JSON.stringify({ event: "typing_stop", payload: { chat_id: activeChatId } }));
@@ -284,7 +301,11 @@ export function MessageComposer() {
placeholder="Write a message..."
value={text}
onChange={(e) => {
setText(e.target.value);
const next = e.target.value;
setText(next);
if (activeChatId) {
setDraft(activeChatId, next);
}
if (activeChatId) {
const ws = getWs();
ws?.send(JSON.stringify({ event: "typing_start", payload: { chat_id: activeChatId } }));

View File

@@ -6,6 +6,7 @@ interface ChatState {
chats: Chat[];
activeChatId: number | null;
messagesByChat: Record<number, Message[]>;
draftsByChat: Record<number, string>;
typingByChat: Record<number, number[]>;
replyToByChat: Record<number, Message | null>;
unreadBoundaryByChat: Record<number, number>;
@@ -38,12 +39,15 @@ interface ChatState {
setReplyToMessage: (chatId: number, message: Message | null) => void;
updateChatPinnedMessage: (chatId: number, pinnedMessageId: number | null) => void;
applyPresenceEvent: (chatId: number, userId: number, isOnline: boolean, lastSeenAt?: string) => void;
setDraft: (chatId: number, text: string) => void;
clearDraft: (chatId: number) => void;
}
export const useChatStore = create<ChatState>((set, get) => ({
chats: [],
activeChatId: null,
messagesByChat: {},
draftsByChat: {},
typingByChat: {},
replyToByChat: {},
unreadBoundaryByChat: {},
@@ -272,5 +276,21 @@ export const useChatStore = create<ChatState>((set, get) => ({
}
return chat;
})
}))
})),
setDraft: (chatId, text) =>
set((state) => ({
draftsByChat: {
...state.draftsByChat,
[chatId]: text
}
})),
clearDraft: (chatId) =>
set((state) => {
if (!(chatId in state.draftsByChat)) {
return state;
}
const next = { ...state.draftsByChat };
delete next[chatId];
return { draftsByChat: next };
})
}));