@antzsoft/chat-core 1.4.0 → 1.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +112 -1
- package/dist/chat.store-UVTDBPEC.js +7 -0
- package/dist/{chunk-EOL5B7GS.js → chunk-UIYJAOGL.js} +4 -2
- package/dist/chunk-UIYJAOGL.js.map +1 -0
- package/dist/index.cjs +25 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +28 -3
- package/dist/index.d.ts +28 -3
- package/dist/index.js +25 -4
- package/dist/index.js.map +1 -1
- package/dist/internal.d.cts +1 -1
- package/dist/internal.d.ts +1 -1
- package/dist/{storage-C_b-SFFH.d.cts → storage-BqygfDAK.d.cts} +22 -1
- package/dist/{storage-C_b-SFFH.d.ts → storage-BqygfDAK.d.ts} +22 -1
- package/docs/integration-guide.html +74 -3
- package/package.json +1 -1
- package/dist/chat.store-DLNRJ5ZT.js +0 -7
- package/dist/chunk-EOL5B7GS.js.map +0 -1
- /package/dist/{chat.store-DLNRJ5ZT.js.map → chat.store-UVTDBPEC.js.map} +0 -0
package/dist/internal.d.cts
CHANGED
package/dist/internal.d.ts
CHANGED
|
@@ -127,6 +127,23 @@ interface MessageReplyReference {
|
|
|
127
127
|
content?: MessageContent;
|
|
128
128
|
sender?: Pick<User, 'displayName' | 'avatarUrl'>;
|
|
129
129
|
}
|
|
130
|
+
/**
|
|
131
|
+
* Present on a message that was created via forwarding. Unlike `MessageReplyReference`,
|
|
132
|
+
* this does NOT carry a content preview — the forwarded message's own `content` already
|
|
133
|
+
* holds the text/attachments, so the UI only needs this to render a "Forwarded" label.
|
|
134
|
+
*
|
|
135
|
+
* `originalMessageId`/`originalConversationId`/`originalSenderId` always point one hop
|
|
136
|
+
* back (the message actually forwarded), never the root of a longer forward chain — the
|
|
137
|
+
* true original author/conversation is intentionally not reconstructable after more than
|
|
138
|
+
* one hop, matching WhatsApp's forwarding privacy model. `forwardDepth` still accumulates
|
|
139
|
+
* across the whole chain, so "Forwarded many times" (depth >= 4) keeps working.
|
|
140
|
+
*/
|
|
141
|
+
interface MessageForwardReference {
|
|
142
|
+
originalMessageId: string;
|
|
143
|
+
originalConversationId: string;
|
|
144
|
+
originalSenderId: string;
|
|
145
|
+
forwardDepth: number;
|
|
146
|
+
}
|
|
130
147
|
/** Metadata attached to system messages by the server. */
|
|
131
148
|
interface SystemMessageMetadata {
|
|
132
149
|
type?: 'system';
|
|
@@ -151,6 +168,8 @@ interface Message {
|
|
|
151
168
|
senderId: string;
|
|
152
169
|
content: MessageContent;
|
|
153
170
|
replyTo?: MessageReplyReference;
|
|
171
|
+
/** Present when this message was created via forwarding rather than composed directly. */
|
|
172
|
+
forwardedFrom?: MessageForwardReference;
|
|
154
173
|
/**
|
|
155
174
|
* Flat list of mentioned userIds (denormalized from the `@[name](id)` tokens in
|
|
156
175
|
* `content.text`). Contains `"all"` for an @all mention. Used for fan-out and
|
|
@@ -221,6 +240,8 @@ interface Conversation {
|
|
|
221
240
|
isPinned?: boolean;
|
|
222
241
|
isMuted?: boolean;
|
|
223
242
|
mutedUntil?: string;
|
|
243
|
+
/** True if the current user manually marked this conversation unread (independent of unreadCount) */
|
|
244
|
+
isManuallyUnread?: boolean;
|
|
224
245
|
/** Last message sequence number in this conversation — use as afterSeq cursor for sync */
|
|
225
246
|
lastSeq?: number;
|
|
226
247
|
}
|
|
@@ -845,4 +866,4 @@ declare function uploadBatchWithSlots(files: UploadableFile[], platformUploadFn:
|
|
|
845
866
|
slotToFile: Map<string, FileResponse>;
|
|
846
867
|
}>;
|
|
847
868
|
|
|
848
|
-
export { type
|
|
869
|
+
export { type MultipartPartUrl as $, type AuthResponse as A, type BatchUploadResult as B, type CrossConversationSyncResponse as C, type ConversationUpdatedEvent as D, type FileSizeLimits as E, type FileResponse as F, type LastReaction as G, MENTION_ALL_ID as H, type MessageAckEvent as I, type MessageContent as J, type MessageDeletedEvent as K, type LoginCredentials as L, type Message as M, type MessageDeletedForMeEvent as N, type MessageDeliveredEvent as O, type PaginatedResponse as P, type MessageForwardReference as Q, type ResolvedCompressionConfig as R, type SendMessagePayload as S, type MessageMetadata as T, type User as U, type MessageReaction as V, type MessageReceiptEntry as W, type MessageReplyReference as X, type MessageStarUpdatedEvent as Y, type MessageUpdatedEvent as Z, type MessagesDeliveredEvent as _, type RegisterData as a, type MultipartUploadInfo as a0, type NewMessageEvent as a1, type OptimisticAttachment as a2, type PlatformCompressFn as a3, type PlatformUploadFn as a4, type PlatformUploadPartFn as a5, type QuietHours as a6, type ReactionGroup as a7, type ReactionUpdatedEvent as a8, type ReactionUser as a9, type ReadReceiptEvent as aa, type ReplyAttachmentSnapshot as ab, type ResolvedFileSizeLimits as ac, type SendMessageAttachment as ad, type SyncDeletedForMe as ae, type SyncDeliveredReceipt as af, type SyncParticipantChange as ag, type SyncReactionEntry as ah, type SyncReactions as ai, type SyncReadReceipt as aj, type SyncStarEntry as ak, type SystemMessageMetadata as al, type TypingIndicatorEvent as am, type UploadConfig as an, type UploadProgress as ao, type UserStatusEvent as ap, resolveConfig as aq, storageApi as ar, uploadBatch as as, uploadBatchWithSlots as at, type AuthTokens as b, type ConversationSyncResponse as c, type AppConfig as d, type CursorPaginatedResponse as e, type MessageReactionsResponse as f, type MessageReceiptsResponse as g, type ConversationListParams as h, type Conversation as i, type Participant as j, type ConversationUnreadCount as k, type UnreadSummary as l, type UserPreferences as m, type ResolvedConfig as n, type PersistStorage as o, type PresignedUrlRequest as p, type PresignedUrlResponse as q, type CompletedPart as r, type FileType as s, type AntzChatConfig as t, type UploadableFile as u, type Attachment as v, type CompressedFile as w, type CompressionAlgorithm as x, type CompressionConfig as y, type ConversationType as z };
|
|
@@ -127,6 +127,23 @@ interface MessageReplyReference {
|
|
|
127
127
|
content?: MessageContent;
|
|
128
128
|
sender?: Pick<User, 'displayName' | 'avatarUrl'>;
|
|
129
129
|
}
|
|
130
|
+
/**
|
|
131
|
+
* Present on a message that was created via forwarding. Unlike `MessageReplyReference`,
|
|
132
|
+
* this does NOT carry a content preview — the forwarded message's own `content` already
|
|
133
|
+
* holds the text/attachments, so the UI only needs this to render a "Forwarded" label.
|
|
134
|
+
*
|
|
135
|
+
* `originalMessageId`/`originalConversationId`/`originalSenderId` always point one hop
|
|
136
|
+
* back (the message actually forwarded), never the root of a longer forward chain — the
|
|
137
|
+
* true original author/conversation is intentionally not reconstructable after more than
|
|
138
|
+
* one hop, matching WhatsApp's forwarding privacy model. `forwardDepth` still accumulates
|
|
139
|
+
* across the whole chain, so "Forwarded many times" (depth >= 4) keeps working.
|
|
140
|
+
*/
|
|
141
|
+
interface MessageForwardReference {
|
|
142
|
+
originalMessageId: string;
|
|
143
|
+
originalConversationId: string;
|
|
144
|
+
originalSenderId: string;
|
|
145
|
+
forwardDepth: number;
|
|
146
|
+
}
|
|
130
147
|
/** Metadata attached to system messages by the server. */
|
|
131
148
|
interface SystemMessageMetadata {
|
|
132
149
|
type?: 'system';
|
|
@@ -151,6 +168,8 @@ interface Message {
|
|
|
151
168
|
senderId: string;
|
|
152
169
|
content: MessageContent;
|
|
153
170
|
replyTo?: MessageReplyReference;
|
|
171
|
+
/** Present when this message was created via forwarding rather than composed directly. */
|
|
172
|
+
forwardedFrom?: MessageForwardReference;
|
|
154
173
|
/**
|
|
155
174
|
* Flat list of mentioned userIds (denormalized from the `@[name](id)` tokens in
|
|
156
175
|
* `content.text`). Contains `"all"` for an @all mention. Used for fan-out and
|
|
@@ -221,6 +240,8 @@ interface Conversation {
|
|
|
221
240
|
isPinned?: boolean;
|
|
222
241
|
isMuted?: boolean;
|
|
223
242
|
mutedUntil?: string;
|
|
243
|
+
/** True if the current user manually marked this conversation unread (independent of unreadCount) */
|
|
244
|
+
isManuallyUnread?: boolean;
|
|
224
245
|
/** Last message sequence number in this conversation — use as afterSeq cursor for sync */
|
|
225
246
|
lastSeq?: number;
|
|
226
247
|
}
|
|
@@ -845,4 +866,4 @@ declare function uploadBatchWithSlots(files: UploadableFile[], platformUploadFn:
|
|
|
845
866
|
slotToFile: Map<string, FileResponse>;
|
|
846
867
|
}>;
|
|
847
868
|
|
|
848
|
-
export { type
|
|
869
|
+
export { type MultipartPartUrl as $, type AuthResponse as A, type BatchUploadResult as B, type CrossConversationSyncResponse as C, type ConversationUpdatedEvent as D, type FileSizeLimits as E, type FileResponse as F, type LastReaction as G, MENTION_ALL_ID as H, type MessageAckEvent as I, type MessageContent as J, type MessageDeletedEvent as K, type LoginCredentials as L, type Message as M, type MessageDeletedForMeEvent as N, type MessageDeliveredEvent as O, type PaginatedResponse as P, type MessageForwardReference as Q, type ResolvedCompressionConfig as R, type SendMessagePayload as S, type MessageMetadata as T, type User as U, type MessageReaction as V, type MessageReceiptEntry as W, type MessageReplyReference as X, type MessageStarUpdatedEvent as Y, type MessageUpdatedEvent as Z, type MessagesDeliveredEvent as _, type RegisterData as a, type MultipartUploadInfo as a0, type NewMessageEvent as a1, type OptimisticAttachment as a2, type PlatformCompressFn as a3, type PlatformUploadFn as a4, type PlatformUploadPartFn as a5, type QuietHours as a6, type ReactionGroup as a7, type ReactionUpdatedEvent as a8, type ReactionUser as a9, type ReadReceiptEvent as aa, type ReplyAttachmentSnapshot as ab, type ResolvedFileSizeLimits as ac, type SendMessageAttachment as ad, type SyncDeletedForMe as ae, type SyncDeliveredReceipt as af, type SyncParticipantChange as ag, type SyncReactionEntry as ah, type SyncReactions as ai, type SyncReadReceipt as aj, type SyncStarEntry as ak, type SystemMessageMetadata as al, type TypingIndicatorEvent as am, type UploadConfig as an, type UploadProgress as ao, type UserStatusEvent as ap, resolveConfig as aq, storageApi as ar, uploadBatch as as, uploadBatchWithSlots as at, type AuthTokens as b, type ConversationSyncResponse as c, type AppConfig as d, type CursorPaginatedResponse as e, type MessageReactionsResponse as f, type MessageReceiptsResponse as g, type ConversationListParams as h, type Conversation as i, type Participant as j, type ConversationUnreadCount as k, type UnreadSummary as l, type UserPreferences as m, type ResolvedConfig as n, type PersistStorage as o, type PresignedUrlRequest as p, type PresignedUrlResponse as q, type CompletedPart as r, type FileType as s, type AntzChatConfig as t, type UploadableFile as u, type Attachment as v, type CompressedFile as w, type CompressionAlgorithm as x, type CompressionConfig as y, type ConversationType as z };
|
|
@@ -155,7 +155,7 @@ section.sec>h2:hover{color:#fff}
|
|
|
155
155
|
|
|
156
156
|
<div class="section-label">What's New</div>
|
|
157
157
|
<ul>
|
|
158
|
-
<li><a href="#whats-new">v1.
|
|
158
|
+
<li><a href="#whats-new">v1.4.1 Release Notes</a></li>
|
|
159
159
|
</ul>
|
|
160
160
|
|
|
161
161
|
<div class="section-label">Getting Started</div>
|
|
@@ -250,12 +250,65 @@ section.sec>h2:hover{color:#fff}
|
|
|
250
250
|
<h2>What's New</h2>
|
|
251
251
|
<p style="color:var(--muted);font-size:13px;margin-bottom:20px">Version history and release notes. Click a version to expand.</p>
|
|
252
252
|
|
|
253
|
-
<!-- ── v1.
|
|
253
|
+
<!-- ── v1.4.1 (current) ── -->
|
|
254
|
+
<div class="wn-version open" id="wn-141">
|
|
255
|
+
<div class="wn-header" onclick="toggleVersion('wn-141')">
|
|
256
|
+
<div class="wn-title">
|
|
257
|
+
<span class="wn-ver">v1.4.1</span>
|
|
258
|
+
<span class="wn-badge current">Current</span>
|
|
259
|
+
<span class="wn-date">July 2026</span>
|
|
260
|
+
</div>
|
|
261
|
+
<span class="wn-chevron">▲</span>
|
|
262
|
+
</div>
|
|
263
|
+
<div class="wn-body">
|
|
264
|
+
<div class="wn-item" id="wn-141-markunread">
|
|
265
|
+
<div class="wn-item-header" onclick="toggleItem('wn-141-markunread')">
|
|
266
|
+
<span class="wn-tag new">New</span>
|
|
267
|
+
<span class="wn-item-title">Manual "mark as unread" — flag a conversation unread independent of unreadCount</span>
|
|
268
|
+
<span class="wn-chevron-sm">▾</span>
|
|
269
|
+
</div>
|
|
270
|
+
<div class="wn-item-body">
|
|
271
|
+
<p>Same UX as WhatsApp/Telegram's "mark as unread": re-flag a conversation you've already read so it stands out again in the list, without fabricating unread messages or touching the read-receipt cursor.</p>
|
|
272
|
+
<p><strong>New field:</strong> <code>Conversation.isManuallyUnread?: boolean</code>. Present on every conversation object from <code>conversationsApi.list/get</code>, and on <code>conversation_created</code>/<code>conversation_updated</code> socket payloads that carry a full conversation DTO. <code>false</code>/absent for a conversation that has never been manually flagged.</p>
|
|
273
|
+
<p><strong>New API methods:</strong> <code>conversationsApi.markUnread(conversationId)</code> and <code>conversationsApi.markRead(conversationId)</code> — both <code>Promise<void></code>, same shape as <code>pin</code>/<code>unpin</code>.</p>
|
|
274
|
+
<p><strong>Auto-clears on read.</strong> Opening the conversation (socket <code>mark_read</code> or the REST notification-catchup path) clears the flag server-side automatically — you don't have to manually un-flag it after reading.</p>
|
|
275
|
+
<p><strong>Live sync.</strong> Toggling the flag emits <code>conversation_updated</code> to the caller's other connected devices/tabs immediately — unlike <code>mute</code>/<code>pin</code>, which currently only take effect on next fetch.</p>
|
|
276
|
+
<p><strong>Backward compatible, additive-only.</strong> <code>isManuallyUnread</code> is optional; an older SDK against a server with this change simply ignores the extra field. <strong>No integration changes required</strong> unless you want to surface the new flag/actions in your own conversation list UI (the RN and web SDKs' built-in <code>ConversationList</code> already do).</p>
|
|
277
|
+
</div>
|
|
278
|
+
</div>
|
|
279
|
+
</div>
|
|
280
|
+
</div><!-- /.wn-version -->
|
|
281
|
+
|
|
282
|
+
<!-- ── v1.4.0 ── -->
|
|
283
|
+
<div class="wn-version open" id="wn-140">
|
|
284
|
+
<div class="wn-header" onclick="toggleVersion('wn-140')">
|
|
285
|
+
<div class="wn-title">
|
|
286
|
+
<span class="wn-ver">v1.4.0</span>
|
|
287
|
+
<span class="wn-date">July 2026</span>
|
|
288
|
+
</div>
|
|
289
|
+
<span class="wn-chevron">▲</span>
|
|
290
|
+
</div>
|
|
291
|
+
<div class="wn-body">
|
|
292
|
+
<div class="wn-item" id="wn-140-transit">
|
|
293
|
+
<div class="wn-item-header" onclick="toggleItem('wn-140-transit')">
|
|
294
|
+
<span class="wn-tag fix">Fix</span>
|
|
295
|
+
<span class="wn-item-title">Stale transit encryption key after token refresh — continuous "Transit decryption failed" spam</span>
|
|
296
|
+
<span class="wn-chevron-sm">▾</span>
|
|
297
|
+
</div>
|
|
298
|
+
<div class="wn-item-body">
|
|
299
|
+
<p><code>reconnectSocket()</code> called <code>socket.connect()</code> to apply a refreshed token while preserving the transit session — but socket.io-client's <code>connect()</code> is a no-op on an already-connected socket. A token refresh fires while the user is actively on a chat screen (socket still connected), so the new auth was never actually sent; the socket kept running on the stale token until the server eventually dropped it, and the disconnect handler then cleared the transit session, desyncing the client's key from the server's — producing continuous <code>"Transit decryption failed for event: user_online"</code>.</p>
|
|
300
|
+
<p><strong>Fix:</strong> in the transit branch of <code>reconnectSocket()</code>, force a real transport re-cycle (disconnect → connect) so the fresh auth (token + <code>transitSessionId</code>) is actually sent and the server re-links the <em>same</em> session (same key), instead of silently continuing on stale auth. A new internal <code>_preservingSession</code> guard stops the disconnect handler from wiping the key being deliberately carried forward. Non-transit and already-disconnected paths are unchanged.</p>
|
|
301
|
+
<p><strong>Backward compatible.</strong> Only affects transit-encryption deployments that refresh auth tokens while the socket is connected. <strong>No integration changes required</strong> — the fix is entirely internal to <code>reconnectSocket()</code>.</p>
|
|
302
|
+
</div>
|
|
303
|
+
</div>
|
|
304
|
+
</div>
|
|
305
|
+
</div><!-- /.wn-version -->
|
|
306
|
+
|
|
307
|
+
<!-- ── v1.3.9 ── -->
|
|
254
308
|
<div class="wn-version open" id="wn-139">
|
|
255
309
|
<div class="wn-header" onclick="toggleVersion('wn-139')">
|
|
256
310
|
<div class="wn-title">
|
|
257
311
|
<span class="wn-ver">v1.3.9</span>
|
|
258
|
-
<span class="wn-badge current">Current</span>
|
|
259
312
|
<span class="wn-date">July 2026</span>
|
|
260
313
|
</div>
|
|
261
314
|
<span class="wn-chevron">▲</span>
|
|
@@ -3337,6 +3390,10 @@ chatClient.<span class="fn">disconnect</span>();
|
|
|
3337
3390
|
<span class="kw">await</span> conversationsApi.<span class="fn">pin</span>(conversationId); <span class="cm">// 400 if already at 5 pinned</span>
|
|
3338
3391
|
<span class="kw">await</span> conversationsApi.<span class="fn">mute</span>(conversationId, <span class="str">'2025-12-31T23:59:59Z'</span>); <span class="cm">// omit date = indefinite</span>
|
|
3339
3392
|
|
|
3393
|
+
<span class="cm">// Mark as unread — flags the conversation independent of unreadCount (v1.4.1+)</span>
|
|
3394
|
+
<span class="kw">await</span> conversationsApi.<span class="fn">markUnread</span>(conversationId);
|
|
3395
|
+
<span class="kw">await</span> conversationsApi.<span class="fn">markRead</span>(conversationId); <span class="cm">// also clears automatically once the user opens the chat</span>
|
|
3396
|
+
|
|
3340
3397
|
<span class="cm">// ── 1:1 Chat ──────────────────────────────────────────────────────────────────</span>
|
|
3341
3398
|
<span class="cm">// "Delete Chat" — hides from caller's list only. Other participant unaffected.</span>
|
|
3342
3399
|
<span class="cm">// If the other person messages again the chat reappears, showing only new messages.</span>
|
|
@@ -3378,6 +3435,20 @@ chatClient.<span class="fn">disconnect</span>();
|
|
|
3378
3435
|
<span class="kw">await</span> conversationsApi.<span class="fn">pin</span>(conversationId);</code></pre>
|
|
3379
3436
|
<p>If you use <code>useConversations()</code> (Web/RN SDK), this check is built in — the <code>pin</code> mutation throws before hitting the network if the limit is reached. The hook also exposes <code>maxPinnedConversations</code> to disable the pin button in your UI.</p>
|
|
3380
3437
|
</div>
|
|
3438
|
+
|
|
3439
|
+
<div class="callout info">
|
|
3440
|
+
<strong>Mark as Unread (v1.4.1+)</strong> — <code>Conversation.isManuallyUnread</code> is a separate flag from <code>unreadCount</code>: it lets a user re-flag an already-read conversation so it stands out again, without fabricating unread messages or moving the read-receipt cursor. Same behavior as WhatsApp/Telegram's "mark as unread" — render it as a plain dot, not a number.
|
|
3441
|
+
<pre style="margin-top:8px"><code><span class="cm">// Numbered badge takes priority; fall back to a dot</span>
|
|
3442
|
+
<span class="kw">const</span> conv = <span class="kw">await</span> conversationsApi.<span class="fn">get</span>(conversationId);
|
|
3443
|
+
<span class="kw">if</span> ((conv.unreadCount ?? 0) > 0) {
|
|
3444
|
+
showBadge(conv.unreadCount);
|
|
3445
|
+
} <span class="kw">else</span> <span class="kw">if</span> (conv.isManuallyUnread) {
|
|
3446
|
+
showDot();
|
|
3447
|
+
}</code></pre>
|
|
3448
|
+
<p><strong>Auto-clears on read</strong> — opening the conversation (socket <code>mark_read</code>, or the REST notification-catchup path) clears the flag server-side automatically. You only need to call <code>markRead()</code> yourself for an explicit "un-flag without opening" action.</p>
|
|
3449
|
+
<p><strong>Live sync</strong> — unlike <code>mute</code>/<code>pin</code> (which only take effect on next fetch), toggling this flag emits <code>conversation_updated</code> to the caller's other connected sessions immediately, so it stays in sync across devices/tabs in real time.</p>
|
|
3450
|
+
<p>If you use <code>useConversations()</code> (Web/RN SDK), it exposes <code>markUnread</code>/<code>markRead</code> mutations with optimistic cache updates, and the built-in <code>ConversationList</code> already renders the dot and the toggle action.</p>
|
|
3451
|
+
</div>
|
|
3381
3452
|
</section>
|
|
3382
3453
|
|
|
3383
3454
|
<!-- ─── STEP 6b: INACTIVE USERS ────────────────────────────────────────── -->
|
package/package.json
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/stores/chat.store.ts"],"sourcesContent":["import { create } from 'zustand';\nimport type { Message } from '../types/index.js';\n\ninterface TypingUser {\n userId: string;\n displayName: string;\n avatarUrl?: string;\n}\n\nexport interface LastReadEntry {\n messageId: string;\n readAt: string;\n}\n\ninterface ChatState {\n activeConversationId: string | null;\n pendingTarget: { conversationId: string; messageId: string } | null;\n typingUsers: Record<string, TypingUser[]>;\n onlineUsers: string[];\n /** keyed by conversationId — current user's last read pointer per conversation */\n lastRead: Record<string, LastReadEntry>;\n /** keyed by userId — last seen timestamp for each user */\n lastSeen: Record<string, string>;\n replyingTo: Message | null;\n editingMessage: Message | null;\n isSidebarOpen: boolean;\n isGroupInfoOpen: boolean;\n isStarredPanelOpen: boolean;\n /** messageId currently shown in the Message Info panel, null = closed */\n messageInfoId: string | null;\n\n setActiveConversation: (id: string | null) => void;\n setPendingTarget: (target: { conversationId: string; messageId: string } | null) => void;\n addTypingUser: (conversationId: string, user: TypingUser) => void;\n removeTypingUser: (conversationId: string, userId: string) => void;\n setUserOnline: (userId: string) => void;\n setUserOffline: (userId: string) => void;\n setOnlineUsers: (userIds: string[]) => void;\n setLastRead: (conversationId: string, messageId: string, readAt: string) => void;\n setLastSeen: (userId: string, lastSeenAt: string | null) => void;\n setReplyingTo: (message: Message | null) => void;\n setEditingMessage: (message: Message | null) => void;\n toggleSidebar: () => void;\n setSidebarOpen: (open: boolean) => void;\n toggleGroupInfo: () => void;\n setGroupInfoOpen: (open: boolean) => void;\n toggleStarredPanel: () => void;\n setStarredPanelOpen: (open: boolean) => void;\n setMessageInfoId: (id: string | null) => void;\n}\n\nexport const useChatStore = create<ChatState>((set) => ({\n activeConversationId: null,\n pendingTarget: null,\n typingUsers: {},\n onlineUsers: [],\n lastRead: {},\n lastSeen: {},\n replyingTo: null,\n editingMessage: null,\n isSidebarOpen: true,\n isGroupInfoOpen: false,\n isStarredPanelOpen: false,\n messageInfoId: null,\n\n setActiveConversation: (id) =>\n set({ activeConversationId: id, replyingTo: null, editingMessage: null }),\n\n setPendingTarget: (target) => set({ pendingTarget: target }),\n\n addTypingUser: (conversationId, user) =>\n set((state) => {\n const existing = state.typingUsers[conversationId] ?? [];\n const deduped = existing.filter((u) => u.userId !== user.userId);\n return { typingUsers: { ...state.typingUsers, [conversationId]: [...deduped, user] } };\n }),\n\n removeTypingUser: (conversationId, userId) =>\n set((state) => ({\n typingUsers: {\n ...state.typingUsers,\n [conversationId]: (state.typingUsers[conversationId] ?? []).filter(\n (u) => u.userId !== userId,\n ),\n },\n })),\n\n setUserOnline: (userId) =>\n set((state) => ({\n onlineUsers: state.onlineUsers.includes(userId)\n ? state.onlineUsers\n : [...state.onlineUsers, userId],\n })),\n\n setUserOffline: (userId) =>\n set((state) => ({ onlineUsers: state.onlineUsers.filter((id) => id !== userId) })),\n\n setOnlineUsers: (userIds) => set({ onlineUsers: userIds }),\n\n setLastRead: (conversationId, messageId, readAt) =>\n set((state) => ({\n lastRead: { ...state.lastRead, [conversationId]: { messageId, readAt } },\n })),\n\n setLastSeen: (userId, lastSeenAt) =>\n set((state) => {\n if (lastSeenAt === null) {\n const { [userId]: _, ...rest } = state.lastSeen;\n return { lastSeen: rest };\n }\n return { lastSeen: { ...state.lastSeen, [userId]: lastSeenAt } };\n }),\n\n setReplyingTo: (message) => set({ replyingTo: message, editingMessage: null }),\n\n setEditingMessage: (message) => set({ editingMessage: message, replyingTo: null }),\n\n toggleSidebar: () => set((state) => ({ isSidebarOpen: !state.isSidebarOpen })),\n setSidebarOpen: (open) => set({ isSidebarOpen: open }),\n\n toggleGroupInfo: () => set((state) => ({ isGroupInfoOpen: !state.isGroupInfoOpen })),\n setGroupInfoOpen: (open) => set({ isGroupInfoOpen: open }),\n\n toggleStarredPanel: () => set((state) => ({ isStarredPanelOpen: !state.isStarredPanelOpen })),\n setStarredPanelOpen: (open) => set({ isStarredPanelOpen: open }),\n\n setMessageInfoId: (id: string | null) => set({ messageInfoId: id }),\n}));\n"],"mappings":"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","names":[]}
|
|
File without changes
|