From 59640c6f44814fc7cd827e7056f2cf53d29f845a Mon Sep 17 00:00:00 2001 From: ZhenYi <434836402@qq.com> Date: Fri, 24 Apr 2026 00:04:36 +0800 Subject: [PATCH] feat(ws-client): add TypingStart/TypingStop protocol types and client handlers ws-protocol.ts: TypingStartPayload/TypingStopPayload interfaces, WsEventPayload union types. room-ws-client.ts: onTypingStart/onTypingStop callbacks, sendTyping() method, event dispatch for typing.start/typing_start. editor/types.ts: special_here/special_channel MentionType + description field on MentionItem. --- src/components/room/message/editor/types.ts | 3 ++- src/lib/room-ws-client.ts | 26 +++++++++++++++++++++ src/lib/ws-protocol.ts | 17 +++++++++++++- 3 files changed, 44 insertions(+), 2 deletions(-) diff --git a/src/components/room/message/editor/types.ts b/src/components/room/message/editor/types.ts index f3894ed..e07a1ff 100644 --- a/src/components/room/message/editor/types.ts +++ b/src/components/room/message/editor/types.ts @@ -2,13 +2,14 @@ * Core types for the IM editor (mentions, files, emojis). */ -export type MentionType = 'user' | 'channel' | 'ai' | 'command'; +export type MentionType = 'user' | 'channel' | 'ai' | 'command' | 'special_here' | 'special_channel'; export interface MentionItem { id: string; label: string; type: MentionType; avatar?: string; + description?: string; // shown under label in suggestion dropdown } export interface FileData { diff --git a/src/lib/room-ws-client.ts b/src/lib/room-ws-client.ts index f0071c7..6130eb8 100644 --- a/src/lib/room-ws-client.ts +++ b/src/lib/room-ws-client.ts @@ -78,6 +78,8 @@ export interface RoomWsCallbacks { onMessagePinned?: (payload: import('./ws-protocol').MessagePinnedPayload) => void; onMessageUnpinned?: (payload: import('./ws-protocol').MessageUnpinnedPayload) => void; onUserPresence?: (payload: UserPresencePayload) => void; + onTypingStart?: (payload: import('./ws-protocol').TypingStartPayload) => void; + onTypingStop?: (payload: import('./ws-protocol').TypingStopPayload) => void; onStatusChange?: (status: RoomWsStatus) => void; onError?: (error: Error) => void; /** Called each time the client sends a heartbeat ping */ @@ -961,6 +963,14 @@ export class RoomWsClient { return url; } + /** Send a typing_start / typing_stop event directly via WebSocket push (no response needed). */ + sendTyping(roomId: string, action: 'start' | 'stop'): void { + if (this.ws && this.status === 'open') { + const event = { type: 'event', event: `typing_${action}`, room_id: roomId }; + this.ws.send(JSON.stringify(event)); + } + } + private handleMessage(rawText: string): void { // Handle raw JSON pong before full parsing — resets heartbeat if (rawText.trim() === '{"type":"pong"}') { @@ -1033,6 +1043,22 @@ export class RoomWsClient { status: ((event.data as { status?: string })?.status ?? 'offline') as 'online' | 'away' | 'dnd' | 'offline', }); break; + case 'typing.start': + case 'typing_start': + this.callbacks.onTypingStart?.({ + room_id: event.room_id ?? '', + user_id: (event.data as { user_id?: string })?.user_id ?? '', + username: (event.data as { username?: string })?.username ?? '', + avatar_url: (event.data as { avatar_url?: string })?.avatar_url, + }); + break; + case 'typing.stop': + case 'typing_stop': + this.callbacks.onTypingStop?.({ + room_id: event.room_id ?? '', + user_id: (event.data as { user_id?: string })?.user_id ?? '', + }); + break; default: // Unknown event type - ignore silently break; diff --git a/src/lib/ws-protocol.ts b/src/lib/ws-protocol.ts index 4bf70b9..5b69535 100644 --- a/src/lib/ws-protocol.ts +++ b/src/lib/ws-protocol.ts @@ -133,7 +133,20 @@ export type WsResponseData = | NotificationListData | MentionListData | SubscribeData - | UserInfo[]; + | UserInfo[] + | null; + +export interface TypingStartPayload { + room_id: string; + user_id: string; + username: string; + avatar_url?: string; +} + +export interface TypingStopPayload { + room_id: string; + user_id: string; +} export interface WsEvent { type: 'event'; @@ -155,6 +168,8 @@ export type WsEventPayload = | { type: 'message_pinned'; data: MessagePinnedPayload } | { type: 'message_unpinned'; data: MessageUnpinnedPayload } | { type: 'user_presence'; data: UserPresencePayload } + | { type: 'typing_start'; data: TypingStartPayload } + | { type: 'typing_stop'; data: TypingStopPayload } | { type: string; data: unknown }; // catch-all for unknown events export interface RoomMessagePayload {