@poolse/react 2.0.13 → 2.1.1
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/dist/index.cjs +87 -7
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +40 -3
- package/dist/index.d.ts +40 -3
- package/dist/index.js +84 -8
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -181,6 +181,15 @@ interface UseMessagesState {
|
|
|
181
181
|
* "tell the server we read up to here".
|
|
182
182
|
*/
|
|
183
183
|
markReadUpTo: (messageId: Uuid) => Promise<void>;
|
|
184
|
+
/**
|
|
185
|
+
* Re-fetch the newest page (PAGE_SIZE) and replace the cached
|
|
186
|
+
* messages. Useful when the app returns from background or the
|
|
187
|
+
* realtime socket reconnects after a drop — any messages we missed
|
|
188
|
+
* while disconnected are pulled in via REST. Pagination resets:
|
|
189
|
+
* `hasMore` becomes true again so `loadMore()` will walk back from
|
|
190
|
+
* the new tail.
|
|
191
|
+
*/
|
|
192
|
+
refetch: () => Promise<void>;
|
|
184
193
|
}
|
|
185
194
|
/**
|
|
186
195
|
* Live message list for `conversationId`. Combines:
|
|
@@ -303,8 +312,15 @@ interface UseTypingState {
|
|
|
303
312
|
}
|
|
304
313
|
/**
|
|
305
314
|
* Tracks who's typing in `conversationId`. Subscribes to the
|
|
306
|
-
* `typing:start` / `typing:stop` broadcasts
|
|
307
|
-
*
|
|
315
|
+
* `typing:start` / `typing:stop` broadcasts, plus two purely
|
|
316
|
+
* client-side eviction paths that keep the chip from lingering when
|
|
317
|
+
* the server is slow (or never) to send `typing:stop`:
|
|
318
|
+
*
|
|
319
|
+
* 1. Per-user inactivity timer — each `typing:start` resets a 4 s
|
|
320
|
+
* timeout for that `external_id`; when it fires we drop them
|
|
321
|
+
* from the set. Covers "they stopped typing without sending".
|
|
322
|
+
* 2. `message:new` from the same sender — if a typing user sends a
|
|
323
|
+
* message, we evict them immediately. Covers "they just sent".
|
|
308
324
|
*/
|
|
309
325
|
declare function useTyping(conversationId: string): UseTypingState;
|
|
310
326
|
|
|
@@ -448,4 +464,25 @@ interface UseUserState {
|
|
|
448
464
|
*/
|
|
449
465
|
declare function useUser(externalId: string | null | undefined): UseUserState;
|
|
450
466
|
|
|
451
|
-
|
|
467
|
+
/**
|
|
468
|
+
* Where this message sits inside a same-sender, same-day, in-window
|
|
469
|
+
* cluster. Drives the bubble's corner treatment + the row's
|
|
470
|
+
* inter-cluster margin.
|
|
471
|
+
*
|
|
472
|
+
* * `standalone` — single bubble, gets the tail
|
|
473
|
+
* * `first` — first of multi; all corners rounded
|
|
474
|
+
* * `middle` — interior; all corners rounded
|
|
475
|
+
* * `last` — last of multi; gets the tail
|
|
476
|
+
*/
|
|
477
|
+
type GroupPosition = 'first' | 'middle' | 'last' | 'standalone';
|
|
478
|
+
declare function computeGroupPosition(msg: Message, prev: Message | null, next: Message | null, windowMs: number): GroupPosition;
|
|
479
|
+
declare function sameGroup(a: Message | null, b: Message | null, windowMs: number): boolean;
|
|
480
|
+
declare function sameDay(a: string, b: string): boolean;
|
|
481
|
+
/**
|
|
482
|
+
* "Today" / "Yesterday" / "Mon, 26 May" / "26 May 2024" — same
|
|
483
|
+
* convention as iMessage / WhatsApp / Telegram. `now` is exposed
|
|
484
|
+
* for deterministic testing; defaults to the current wall clock.
|
|
485
|
+
*/
|
|
486
|
+
declare function formatDayLabel(iso: string, now?: Date): string;
|
|
487
|
+
|
|
488
|
+
export { type GroupPosition, PoolseProvider, type PoolseProviderProps, type ReactionMap, type UploadItem, type UploadItemStatus, type UseUserState, computeGroupPosition, formatDayLabel, sameDay, sameGroup, useAttachmentUpload, useAttachmentUrl, useConversation, useConversations, useMe, useMembers, useMessages, usePoolse, usePresence, useReactions, useRealtimeStatus, useThread, useTyping, useUser };
|
package/dist/index.js
CHANGED
|
@@ -315,6 +315,7 @@ function useMessages(conversationId) {
|
|
|
315
315
|
const [error, setError] = useState(null);
|
|
316
316
|
const [hasMore, setHasMore] = useState(true);
|
|
317
317
|
const oldestSequenceRef = useRef(null);
|
|
318
|
+
const refetchRef = useRef(null);
|
|
318
319
|
const meIdRef = useRef(me?.id ?? null);
|
|
319
320
|
meIdRef.current = me?.id ?? null;
|
|
320
321
|
const meExternalIdRef = useRef(me?.external_id ?? null);
|
|
@@ -339,6 +340,13 @@ function useMessages(conversationId) {
|
|
|
339
340
|
setError(err instanceof Error ? err : new Error(String(err)));
|
|
340
341
|
setLoading(false);
|
|
341
342
|
});
|
|
343
|
+
refetchRef.current = async () => {
|
|
344
|
+
const page = await handle.list({ limit: PAGE_SIZE });
|
|
345
|
+
const ordered = [...page.data].reverse();
|
|
346
|
+
setMessages(ordered);
|
|
347
|
+
setHasMore(page.data.length === PAGE_SIZE);
|
|
348
|
+
oldestSequenceRef.current = ordered[0]?.sequence ?? null;
|
|
349
|
+
};
|
|
342
350
|
const conv = chat.realtime.conversation(conversationId);
|
|
343
351
|
const offNew = conv.onMessage((msg) => {
|
|
344
352
|
if (msg.thread_root_id) {
|
|
@@ -488,6 +496,11 @@ function useMessages(conversationId) {
|
|
|
488
496
|
},
|
|
489
497
|
[chat, conversationId]
|
|
490
498
|
);
|
|
499
|
+
const refetch = useCallback(async () => {
|
|
500
|
+
const fn = refetchRef.current;
|
|
501
|
+
if (!fn) return;
|
|
502
|
+
await fn();
|
|
503
|
+
}, []);
|
|
491
504
|
return {
|
|
492
505
|
messages,
|
|
493
506
|
loading,
|
|
@@ -497,7 +510,8 @@ function useMessages(conversationId) {
|
|
|
497
510
|
send,
|
|
498
511
|
edit,
|
|
499
512
|
delete: deleteMessage,
|
|
500
|
-
markReadUpTo
|
|
513
|
+
markReadUpTo,
|
|
514
|
+
refetch
|
|
501
515
|
};
|
|
502
516
|
}
|
|
503
517
|
function upsertById(prev, msg) {
|
|
@@ -734,6 +748,7 @@ function upsertById2(prev, msg) {
|
|
|
734
748
|
next[idx] = msg;
|
|
735
749
|
return next;
|
|
736
750
|
}
|
|
751
|
+
var TYPING_TIMEOUT_MS = 4e3;
|
|
737
752
|
function useTyping(conversationId) {
|
|
738
753
|
const chat = usePoolse();
|
|
739
754
|
const { me } = useMe();
|
|
@@ -743,11 +758,31 @@ function useTyping(conversationId) {
|
|
|
743
758
|
meExtRef.current = me?.external_id ?? null;
|
|
744
759
|
useEffect(() => {
|
|
745
760
|
setTyping(/* @__PURE__ */ new Set());
|
|
761
|
+
const timers = /* @__PURE__ */ new Map();
|
|
762
|
+
const evict = (ext) => {
|
|
763
|
+
const t = timers.get(ext);
|
|
764
|
+
if (t) {
|
|
765
|
+
clearTimeout(t);
|
|
766
|
+
timers.delete(ext);
|
|
767
|
+
}
|
|
768
|
+
setTyping((prev) => {
|
|
769
|
+
if (!prev.has(ext)) return prev;
|
|
770
|
+
const next = new Set(prev);
|
|
771
|
+
next.delete(ext);
|
|
772
|
+
return next;
|
|
773
|
+
});
|
|
774
|
+
};
|
|
746
775
|
const conv = chat.realtime.conversation(conversationId);
|
|
747
776
|
const offStart = conv.onTypingStart((evt) => {
|
|
748
777
|
const ext = evt.external_id;
|
|
749
778
|
if (!ext) return;
|
|
750
779
|
if (meExtRef.current && ext === meExtRef.current) return;
|
|
780
|
+
const existing = timers.get(ext);
|
|
781
|
+
if (existing) clearTimeout(existing);
|
|
782
|
+
timers.set(
|
|
783
|
+
ext,
|
|
784
|
+
setTimeout(() => evict(ext), TYPING_TIMEOUT_MS)
|
|
785
|
+
);
|
|
751
786
|
setTyping((prev) => {
|
|
752
787
|
if (prev.has(ext)) return prev;
|
|
753
788
|
const next = new Set(prev);
|
|
@@ -759,16 +794,20 @@ function useTyping(conversationId) {
|
|
|
759
794
|
const ext = evt.external_id;
|
|
760
795
|
if (!ext) return;
|
|
761
796
|
if (meExtRef.current && ext === meExtRef.current) return;
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
797
|
+
evict(ext);
|
|
798
|
+
});
|
|
799
|
+
const offMessage = conv.onMessage((msg) => {
|
|
800
|
+
const ext = msg.sender_external_id;
|
|
801
|
+
if (!ext) return;
|
|
802
|
+
if (meExtRef.current && ext === meExtRef.current) return;
|
|
803
|
+
evict(ext);
|
|
768
804
|
});
|
|
769
805
|
return () => {
|
|
770
806
|
offStart();
|
|
771
807
|
offStop();
|
|
808
|
+
offMessage();
|
|
809
|
+
for (const t of timers.values()) clearTimeout(t);
|
|
810
|
+
timers.clear();
|
|
772
811
|
};
|
|
773
812
|
}, [chat, conversationId]);
|
|
774
813
|
const signalTyping = useCallback(() => {
|
|
@@ -985,6 +1024,43 @@ function useUser(externalId) {
|
|
|
985
1024
|
return state;
|
|
986
1025
|
}
|
|
987
1026
|
|
|
988
|
-
|
|
1027
|
+
// src/grouping.ts
|
|
1028
|
+
function computeGroupPosition(msg, prev, next, windowMs) {
|
|
1029
|
+
const continuesPrev = sameGroup(msg, prev, windowMs);
|
|
1030
|
+
const continuesNext = sameGroup(next, msg, windowMs);
|
|
1031
|
+
if (continuesPrev && continuesNext) return "middle";
|
|
1032
|
+
if (continuesPrev) return "last";
|
|
1033
|
+
if (continuesNext) return "first";
|
|
1034
|
+
return "standalone";
|
|
1035
|
+
}
|
|
1036
|
+
function sameGroup(a, b, windowMs) {
|
|
1037
|
+
if (!a || !b) return false;
|
|
1038
|
+
if (a.sender_id !== b.sender_id) return false;
|
|
1039
|
+
if (!sameDay(a.inserted_at, b.inserted_at)) return false;
|
|
1040
|
+
const ta = new Date(a.inserted_at).getTime();
|
|
1041
|
+
const tb = new Date(b.inserted_at).getTime();
|
|
1042
|
+
return Math.abs(ta - tb) <= windowMs;
|
|
1043
|
+
}
|
|
1044
|
+
function sameDay(a, b) {
|
|
1045
|
+
const da = new Date(a);
|
|
1046
|
+
const db = new Date(b);
|
|
1047
|
+
return da.getFullYear() === db.getFullYear() && da.getMonth() === db.getMonth() && da.getDate() === db.getDate();
|
|
1048
|
+
}
|
|
1049
|
+
function formatDayLabel(iso, now = /* @__PURE__ */ new Date()) {
|
|
1050
|
+
const d = new Date(iso);
|
|
1051
|
+
const startOfDay = (x) => new Date(x.getFullYear(), x.getMonth(), x.getDate()).getTime();
|
|
1052
|
+
const diffDays = Math.floor((startOfDay(now) - startOfDay(d)) / 864e5);
|
|
1053
|
+
if (diffDays === 0) return "Today";
|
|
1054
|
+
if (diffDays === 1) return "Yesterday";
|
|
1055
|
+
if (diffDays < 7) {
|
|
1056
|
+
return d.toLocaleDateString(void 0, { weekday: "long" });
|
|
1057
|
+
}
|
|
1058
|
+
if (d.getFullYear() === now.getFullYear()) {
|
|
1059
|
+
return d.toLocaleDateString(void 0, { weekday: "short", day: "numeric", month: "short" });
|
|
1060
|
+
}
|
|
1061
|
+
return d.toLocaleDateString(void 0, { day: "numeric", month: "short", year: "numeric" });
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
export { PoolseProvider, computeGroupPosition, formatDayLabel, sameDay, sameGroup, useAttachmentUpload, useAttachmentUrl, useConversation, useConversations, useMe, useMembers, useMessages, usePoolse, usePresence, useReactions, useRealtimeStatus, useThread, useTyping, useUser };
|
|
989
1065
|
//# sourceMappingURL=index.js.map
|
|
990
1066
|
//# sourceMappingURL=index.js.map
|