@natoe/colab 0.1.13 → 0.1.16
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.d.mts +120 -14
- package/dist/index.d.ts +120 -14
- package/dist/index.js +317 -110
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +317 -110
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -63,9 +63,29 @@ function toCamelKey(key) {
|
|
|
63
63
|
}
|
|
64
64
|
|
|
65
65
|
// src/core/socket.ts
|
|
66
|
+
function normalizeUnreadPayload(payload) {
|
|
67
|
+
if (payload && typeof payload === "object") {
|
|
68
|
+
const obj = payload;
|
|
69
|
+
if (obj.conversation && typeof obj.conversation === "object") {
|
|
70
|
+
return {
|
|
71
|
+
conversation: obj.conversation,
|
|
72
|
+
order: obj.order && typeof obj.order === "object" ? obj.order : {}
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
return { conversation: payload, order: {} };
|
|
76
|
+
}
|
|
77
|
+
return { conversation: {}, order: {} };
|
|
78
|
+
}
|
|
66
79
|
var CollabSocket = class {
|
|
67
80
|
constructor() {
|
|
68
81
|
this.socket = null;
|
|
82
|
+
/**
|
|
83
|
+
* Conversation channels keyed by id. Each entry is reference-counted so
|
|
84
|
+
* multiple surfaces (e.g. inline chat + expanded panel mounted at once
|
|
85
|
+
* for the same conversation) can coexist without one's `leaveConversation`
|
|
86
|
+
* tearing the channel out from under the other. See `joinConversation`
|
|
87
|
+
* and the returned `ChannelSubscription.release`.
|
|
88
|
+
*/
|
|
69
89
|
this.channels = /* @__PURE__ */ new Map();
|
|
70
90
|
this.presences = /* @__PURE__ */ new Map();
|
|
71
91
|
this.userChannel = null;
|
|
@@ -121,7 +141,7 @@ var CollabSocket = class {
|
|
|
121
141
|
if (!this.socket || !this.config) return;
|
|
122
142
|
this.userChannel = this.socket.channel("user_notifications", {});
|
|
123
143
|
this.userChannel.on("unread_update", (payload) => {
|
|
124
|
-
this.onUnreadUpdate?.(payload);
|
|
144
|
+
this.onUnreadUpdate?.(normalizeUnreadPayload(payload));
|
|
125
145
|
});
|
|
126
146
|
this.userChannel.join().receive("ok", () => {
|
|
127
147
|
}).receive("error", (reason) => {
|
|
@@ -132,63 +152,91 @@ var CollabSocket = class {
|
|
|
132
152
|
});
|
|
133
153
|
});
|
|
134
154
|
}
|
|
135
|
-
/** Register callback for unread count changes
|
|
155
|
+
/** Register callback for unread count changes. The callback receives
|
|
156
|
+
* the normalised {@link UnreadCounts} shape regardless of which
|
|
157
|
+
* payload format the backend pushed. */
|
|
136
158
|
onUnreadCountUpdate(callback) {
|
|
137
159
|
this.onUnreadUpdate = callback;
|
|
138
160
|
}
|
|
139
|
-
/**
|
|
161
|
+
/**
|
|
162
|
+
* Join a conversation channel and subscribe to events.
|
|
163
|
+
*
|
|
164
|
+
* Reference-counted: multiple callers can join the same conversation
|
|
165
|
+
* (e.g. inline preview + expanded panel mounted side-by-side). Each
|
|
166
|
+
* call binds its own listeners and gets back a `ChannelSubscription`.
|
|
167
|
+
* The underlying channel only `.leave()`s the server when the LAST
|
|
168
|
+
* subscriber calls `release()`.
|
|
169
|
+
*/
|
|
140
170
|
joinConversation(conversationId, callbacks) {
|
|
141
171
|
if (!this.socket) return null;
|
|
142
|
-
|
|
143
|
-
|
|
172
|
+
let entry = this.channels.get(conversationId);
|
|
173
|
+
if (!entry) {
|
|
174
|
+
const channel2 = this.socket.channel(`conversation:${conversationId}`, {});
|
|
175
|
+
channel2.join().receive("ok", () => {
|
|
176
|
+
}).receive("error", (reason) => {
|
|
177
|
+
this.config?.onError?.({
|
|
178
|
+
code: "CHANNEL_JOIN_ERROR",
|
|
179
|
+
message: `Failed to join conversation ${conversationId}`,
|
|
180
|
+
details: reason
|
|
181
|
+
});
|
|
182
|
+
});
|
|
183
|
+
entry = { channel: channel2, subscribers: 0, presence: null };
|
|
184
|
+
this.channels.set(conversationId, entry);
|
|
144
185
|
}
|
|
145
|
-
const channel =
|
|
186
|
+
const channel = entry.channel;
|
|
187
|
+
const refs = [];
|
|
188
|
+
const bind = (event, fn) => {
|
|
189
|
+
const ref = channel.on(event, fn);
|
|
190
|
+
refs.push({ event, ref });
|
|
191
|
+
};
|
|
146
192
|
if (callbacks.onMessage) {
|
|
147
|
-
|
|
193
|
+
bind(EVENTS.MESSAGE_NEW, (payload) => {
|
|
148
194
|
callbacks.onMessage(snakeToCamel(payload));
|
|
149
195
|
});
|
|
150
196
|
}
|
|
151
197
|
if (callbacks.onTyping) {
|
|
152
|
-
|
|
198
|
+
bind(EVENTS.USER_TYPING, (payload) => {
|
|
153
199
|
callbacks.onTyping(snakeToCamel(payload));
|
|
154
200
|
});
|
|
155
201
|
}
|
|
156
202
|
if (callbacks.onUserJoined) {
|
|
157
|
-
|
|
203
|
+
bind(EVENTS.USER_JOINED, (payload) => {
|
|
158
204
|
callbacks.onUserJoined(snakeToCamel(payload));
|
|
159
205
|
});
|
|
160
206
|
}
|
|
161
207
|
if (callbacks.onUserLeft) {
|
|
162
|
-
|
|
208
|
+
bind(EVENTS.USER_LEFT, (payload) => {
|
|
163
209
|
callbacks.onUserLeft(snakeToCamel(payload));
|
|
164
210
|
});
|
|
165
211
|
}
|
|
166
212
|
if (callbacks.onChannelUpdated) {
|
|
167
|
-
|
|
213
|
+
bind(EVENTS.CHANNEL_UPDATED, (payload) => {
|
|
168
214
|
callbacks.onChannelUpdated(snakeToCamel(payload));
|
|
169
215
|
});
|
|
170
216
|
}
|
|
171
217
|
if (callbacks.onChannelDeleted) {
|
|
172
|
-
|
|
218
|
+
bind(EVENTS.CHANNEL_DELETED, () => {
|
|
173
219
|
callbacks.onChannelDeleted();
|
|
174
220
|
});
|
|
175
221
|
}
|
|
176
222
|
if (callbacks.onMessageRead) {
|
|
177
|
-
|
|
178
|
-
callbacks.onMessageRead(
|
|
223
|
+
bind(EVENTS.MESSAGE_READ, (payload) => {
|
|
224
|
+
callbacks.onMessageRead(
|
|
225
|
+
snakeToCamel(payload)
|
|
226
|
+
);
|
|
179
227
|
});
|
|
180
228
|
}
|
|
181
229
|
if (callbacks.onMessagePinned) {
|
|
182
|
-
|
|
230
|
+
bind(EVENTS.MESSAGE_PINNED, (payload) => {
|
|
183
231
|
callbacks.onMessagePinned(snakeToCamel(payload));
|
|
184
232
|
});
|
|
185
233
|
}
|
|
186
234
|
if (callbacks.onMessageUnpinned) {
|
|
187
|
-
|
|
235
|
+
bind(EVENTS.MESSAGE_UNPINNED, (payload) => {
|
|
188
236
|
callbacks.onMessageUnpinned(snakeToCamel(payload));
|
|
189
237
|
});
|
|
190
238
|
}
|
|
191
|
-
if (callbacks.onPresence) {
|
|
239
|
+
if (callbacks.onPresence && !entry.presence) {
|
|
192
240
|
const presence = new phoenix.Presence(channel);
|
|
193
241
|
presence.onSync(() => {
|
|
194
242
|
const online = {};
|
|
@@ -197,27 +245,45 @@ var CollabSocket = class {
|
|
|
197
245
|
});
|
|
198
246
|
callbacks.onPresence(online);
|
|
199
247
|
});
|
|
248
|
+
entry.presence = presence;
|
|
200
249
|
this.presences.set(conversationId, presence);
|
|
201
250
|
}
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
251
|
+
entry.subscribers += 1;
|
|
252
|
+
let released = false;
|
|
253
|
+
const release = () => {
|
|
254
|
+
if (released) return;
|
|
255
|
+
released = true;
|
|
256
|
+
const current = this.channels.get(conversationId);
|
|
257
|
+
if (!current) return;
|
|
258
|
+
for (const { event, ref } of refs) {
|
|
259
|
+
current.channel.off(event, ref);
|
|
260
|
+
}
|
|
261
|
+
current.subscribers -= 1;
|
|
262
|
+
if (current.subscribers <= 0) {
|
|
263
|
+
current.channel.leave();
|
|
264
|
+
this.channels.delete(conversationId);
|
|
265
|
+
this.presences.delete(conversationId);
|
|
266
|
+
}
|
|
267
|
+
};
|
|
268
|
+
return { channel, release };
|
|
212
269
|
}
|
|
213
|
-
/**
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
270
|
+
/**
|
|
271
|
+
* @deprecated Use the `release()` method returned by `joinConversation()`.
|
|
272
|
+
* Kept as a no-op so older callers don't throw — but it cannot identify
|
|
273
|
+
* which subscriber should leave, so it silently does nothing. Any code
|
|
274
|
+
* still calling this will leak listeners and prevent the channel from
|
|
275
|
+
* ever being torn down. Migrate to the subscription handle.
|
|
276
|
+
*/
|
|
277
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
278
|
+
leaveConversation(_conversationId) {
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* Look up the underlying Phoenix Channel for a conversation, if any
|
|
282
|
+
* subscriber is still holding it. All send/push paths go through this
|
|
283
|
+
* helper so the refcounted entry shape is contained to joinConversation.
|
|
284
|
+
*/
|
|
285
|
+
getChannel(conversationId) {
|
|
286
|
+
return this.channels.get(conversationId)?.channel ?? null;
|
|
221
287
|
}
|
|
222
288
|
/** Send a message to a conversation.
|
|
223
289
|
*
|
|
@@ -229,7 +295,7 @@ var CollabSocket = class {
|
|
|
229
295
|
*/
|
|
230
296
|
sendMessage(conversationId, payload) {
|
|
231
297
|
return new Promise((resolve, reject) => {
|
|
232
|
-
const channel = this.
|
|
298
|
+
const channel = this.getChannel(conversationId);
|
|
233
299
|
if (!channel) {
|
|
234
300
|
reject({ code: "NOT_JOINED", message: "Not joined to this conversation" });
|
|
235
301
|
return;
|
|
@@ -252,7 +318,7 @@ var CollabSocket = class {
|
|
|
252
318
|
}
|
|
253
319
|
/** Broadcast typing indicator */
|
|
254
320
|
sendTyping(conversationId, isTyping) {
|
|
255
|
-
const channel = this.
|
|
321
|
+
const channel = this.getChannel(conversationId);
|
|
256
322
|
channel?.push(EVENTS.USER_TYPING, {
|
|
257
323
|
userId: this.config?.userId,
|
|
258
324
|
userName: this.config?.userName,
|
|
@@ -261,7 +327,7 @@ var CollabSocket = class {
|
|
|
261
327
|
}
|
|
262
328
|
/** Mark messages as read */
|
|
263
329
|
markAsRead(conversationId, messageId) {
|
|
264
|
-
const channel = this.
|
|
330
|
+
const channel = this.getChannel(conversationId);
|
|
265
331
|
channel?.push(EVENTS.MESSAGE_READ, {
|
|
266
332
|
messageId,
|
|
267
333
|
userId: this.config?.userId
|
|
@@ -295,7 +361,7 @@ var CollabSocket = class {
|
|
|
295
361
|
/** Generic push with promise wrapper */
|
|
296
362
|
channelPush(conversationId, event, payload) {
|
|
297
363
|
return new Promise((resolve, reject) => {
|
|
298
|
-
const channel = this.
|
|
364
|
+
const channel = this.getChannel(conversationId);
|
|
299
365
|
if (!channel) {
|
|
300
366
|
reject({ code: "NOT_JOINED", message: "Not joined to this conversation" });
|
|
301
367
|
return;
|
|
@@ -312,7 +378,7 @@ var CollabSocket = class {
|
|
|
312
378
|
}
|
|
313
379
|
/** Disconnect socket and leave all channels */
|
|
314
380
|
disconnect() {
|
|
315
|
-
this.channels.forEach((
|
|
381
|
+
this.channels.forEach((entry) => entry.channel.leave());
|
|
316
382
|
this.channels.clear();
|
|
317
383
|
this.presences.clear();
|
|
318
384
|
this.userChannel?.leave();
|
|
@@ -367,7 +433,21 @@ var THEME_VAR = {
|
|
|
367
433
|
dangerBg: "--natoe-colab-danger-bg",
|
|
368
434
|
dangerBorder: "--natoe-colab-danger-border",
|
|
369
435
|
dangerFg: "--natoe-colab-danger-fg",
|
|
370
|
-
fontStack: "--natoe-colab-font-stack"
|
|
436
|
+
fontStack: "--natoe-colab-font-stack",
|
|
437
|
+
// Neutral surface palette — promoted to CSS vars so consumers (e.g.
|
|
438
|
+
// CollabPanel themeMode='dark') can flip the whole grayscale at a subtree
|
|
439
|
+
// level without re-themeing every component.
|
|
440
|
+
white: "--natoe-colab-white",
|
|
441
|
+
neutral50: "--natoe-colab-neutral-50",
|
|
442
|
+
neutral100: "--natoe-colab-neutral-100",
|
|
443
|
+
neutral200: "--natoe-colab-neutral-200",
|
|
444
|
+
neutral300: "--natoe-colab-neutral-300",
|
|
445
|
+
neutral400: "--natoe-colab-neutral-400",
|
|
446
|
+
neutral500: "--natoe-colab-neutral-500",
|
|
447
|
+
neutral600: "--natoe-colab-neutral-600",
|
|
448
|
+
neutral700: "--natoe-colab-neutral-700",
|
|
449
|
+
neutral800: "--natoe-colab-neutral-800",
|
|
450
|
+
neutral900: "--natoe-colab-neutral-900"
|
|
371
451
|
};
|
|
372
452
|
var THEME_DEFAULTS = {
|
|
373
453
|
primary: "#2563eb",
|
|
@@ -380,18 +460,9 @@ var THEME_DEFAULTS = {
|
|
|
380
460
|
dangerBg: "#fef2f2",
|
|
381
461
|
dangerBorder: "#fecaca",
|
|
382
462
|
dangerFg: "#b91c1c",
|
|
383
|
-
fontStack: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif'
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
var COLOR = {
|
|
387
|
-
// Brand — themeable
|
|
388
|
-
primary: cssVar(THEME_VAR.primary, THEME_DEFAULTS.primary),
|
|
389
|
-
primaryHover: cssVar(THEME_VAR.primaryHover, THEME_DEFAULTS.primaryHover),
|
|
390
|
-
/** Tinted surface for chips/cards on brand-coloured states. */
|
|
391
|
-
primaryBg: cssVar(THEME_VAR.primaryBg, THEME_DEFAULTS.primaryBg),
|
|
392
|
-
primaryFg: cssVar(THEME_VAR.primaryFg, THEME_DEFAULTS.primaryFg),
|
|
393
|
-
// Neutral grayscale — not themeable, taken from Tailwind's zinc-leaning
|
|
394
|
-
// slate to match the package's existing tonal balance.
|
|
463
|
+
fontStack: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif',
|
|
464
|
+
// Neutral grayscale — these defaults are the same hex literals the
|
|
465
|
+
// package shipped with; they're now overridable per-subtree.
|
|
395
466
|
white: "#ffffff",
|
|
396
467
|
neutral50: "#f9fafb",
|
|
397
468
|
neutral100: "#f3f4f6",
|
|
@@ -402,7 +473,32 @@ var COLOR = {
|
|
|
402
473
|
neutral600: "#4b5563",
|
|
403
474
|
neutral700: "#374151",
|
|
404
475
|
neutral800: "#1f2937",
|
|
405
|
-
neutral900: "#111827"
|
|
476
|
+
neutral900: "#111827"
|
|
477
|
+
};
|
|
478
|
+
var cssVar = (name, fallback) => `var(${name}, ${fallback})`;
|
|
479
|
+
var COLOR = {
|
|
480
|
+
// Brand — themeable
|
|
481
|
+
primary: cssVar(THEME_VAR.primary, THEME_DEFAULTS.primary),
|
|
482
|
+
primaryHover: cssVar(THEME_VAR.primaryHover, THEME_DEFAULTS.primaryHover),
|
|
483
|
+
/** Tinted surface for chips/cards on brand-coloured states. */
|
|
484
|
+
primaryBg: cssVar(THEME_VAR.primaryBg, THEME_DEFAULTS.primaryBg),
|
|
485
|
+
primaryFg: cssVar(THEME_VAR.primaryFg, THEME_DEFAULTS.primaryFg),
|
|
486
|
+
// Neutral grayscale — themeable via CSS variables. Light-mode defaults
|
|
487
|
+
// taken from Tailwind's zinc-leaning slate to match the package's
|
|
488
|
+
// existing tonal balance; a host can override the entire palette by
|
|
489
|
+
// setting --natoe-colab-* values on any ancestor element (used by
|
|
490
|
+
// CollabPanel themeMode='dark' for the viewer's left-panel surface).
|
|
491
|
+
white: cssVar(THEME_VAR.white, THEME_DEFAULTS.white),
|
|
492
|
+
neutral50: cssVar(THEME_VAR.neutral50, THEME_DEFAULTS.neutral50),
|
|
493
|
+
neutral100: cssVar(THEME_VAR.neutral100, THEME_DEFAULTS.neutral100),
|
|
494
|
+
neutral200: cssVar(THEME_VAR.neutral200, THEME_DEFAULTS.neutral200),
|
|
495
|
+
neutral300: cssVar(THEME_VAR.neutral300, THEME_DEFAULTS.neutral300),
|
|
496
|
+
neutral400: cssVar(THEME_VAR.neutral400, THEME_DEFAULTS.neutral400),
|
|
497
|
+
neutral500: cssVar(THEME_VAR.neutral500, THEME_DEFAULTS.neutral500),
|
|
498
|
+
neutral600: cssVar(THEME_VAR.neutral600, THEME_DEFAULTS.neutral600),
|
|
499
|
+
neutral700: cssVar(THEME_VAR.neutral700, THEME_DEFAULTS.neutral700),
|
|
500
|
+
neutral800: cssVar(THEME_VAR.neutral800, THEME_DEFAULTS.neutral800),
|
|
501
|
+
neutral900: cssVar(THEME_VAR.neutral900, THEME_DEFAULTS.neutral900),
|
|
406
502
|
slate800: "#1e293b",
|
|
407
503
|
// Semantic — themeable
|
|
408
504
|
success: cssVar(THEME_VAR.success, THEME_DEFAULTS.success),
|
|
@@ -502,6 +598,7 @@ function CollabProvider({ config, apiBaseUrl, children }) {
|
|
|
502
598
|
return new CollabSocket();
|
|
503
599
|
});
|
|
504
600
|
const [unreadCounts, setUnreadCounts] = React4.useState({});
|
|
601
|
+
const [unreadCountsByOrder, setUnreadCountsByOrder] = React4.useState({});
|
|
505
602
|
const pendingOrderIds = React4.useRef(/* @__PURE__ */ new Set());
|
|
506
603
|
const pendingResolvers = React4.useRef(/* @__PURE__ */ new Map());
|
|
507
604
|
const previewCache = React4.useRef(/* @__PURE__ */ new Map());
|
|
@@ -517,7 +614,8 @@ function CollabProvider({ config, apiBaseUrl, children }) {
|
|
|
517
614
|
setSocket(s);
|
|
518
615
|
}
|
|
519
616
|
s.onUnreadCountUpdate((counts) => {
|
|
520
|
-
setUnreadCounts(counts);
|
|
617
|
+
setUnreadCounts(counts.conversation);
|
|
618
|
+
setUnreadCountsByOrder(counts.order);
|
|
521
619
|
previewCache.current.clear();
|
|
522
620
|
});
|
|
523
621
|
if (!s.isConnected()) {
|
|
@@ -725,6 +823,7 @@ function CollabProvider({ config, apiBaseUrl, children }) {
|
|
|
725
823
|
apiBaseUrl,
|
|
726
824
|
totalUnread,
|
|
727
825
|
unreadCounts,
|
|
826
|
+
unreadCountsByOrder,
|
|
728
827
|
requestPreview,
|
|
729
828
|
invalidatePreview,
|
|
730
829
|
fetchMessages,
|
|
@@ -781,6 +880,7 @@ function useConversation({
|
|
|
781
880
|
const [isConnected, setIsConnected] = React4.useState(false);
|
|
782
881
|
const [replyTo, setReplyTo] = React4.useState(null);
|
|
783
882
|
const joinedConversationId = React4.useRef(null);
|
|
883
|
+
const channelSubscription = React4.useRef(null);
|
|
784
884
|
const typingTimers = React4.useRef(/* @__PURE__ */ new Map());
|
|
785
885
|
const ensureConversationInFlight = React4.useRef(null);
|
|
786
886
|
const markedReadIdsRef = React4.useRef(/* @__PURE__ */ new Set());
|
|
@@ -794,7 +894,9 @@ function useConversation({
|
|
|
794
894
|
const joinChannel = React4.useCallback(
|
|
795
895
|
(conv) => {
|
|
796
896
|
if (joinedConversationId.current === conv.id) return;
|
|
797
|
-
|
|
897
|
+
channelSubscription.current?.release();
|
|
898
|
+
channelSubscription.current = null;
|
|
899
|
+
const subscription = socket.joinConversation(conv.id, {
|
|
798
900
|
// Dedupe by id so an optimistic message (added client-side on send)
|
|
799
901
|
// doesn't double up when the server's broadcast arrives.
|
|
800
902
|
onMessage: (msg) => setMessages((prev) => {
|
|
@@ -844,6 +946,8 @@ function useConversation({
|
|
|
844
946
|
onChannelDeleted: () => {
|
|
845
947
|
setConversation(null);
|
|
846
948
|
setMessages([]);
|
|
949
|
+
channelSubscription.current?.release();
|
|
950
|
+
channelSubscription.current = null;
|
|
847
951
|
joinedConversationId.current = null;
|
|
848
952
|
setIsConnected(false);
|
|
849
953
|
},
|
|
@@ -869,6 +973,7 @@ function useConversation({
|
|
|
869
973
|
);
|
|
870
974
|
}
|
|
871
975
|
});
|
|
976
|
+
channelSubscription.current = subscription;
|
|
872
977
|
joinedConversationId.current = conv.id;
|
|
873
978
|
setIsConnected(true);
|
|
874
979
|
},
|
|
@@ -929,10 +1034,9 @@ function useConversation({
|
|
|
929
1034
|
init();
|
|
930
1035
|
return () => {
|
|
931
1036
|
cancelled = true;
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
}
|
|
1037
|
+
channelSubscription.current?.release();
|
|
1038
|
+
channelSubscription.current = null;
|
|
1039
|
+
joinedConversationId.current = null;
|
|
936
1040
|
typingTimers.current.forEach((timer) => clearTimeout(timer));
|
|
937
1041
|
typingTimers.current.clear();
|
|
938
1042
|
markedReadIdsRef.current.clear();
|
|
@@ -1348,17 +1452,26 @@ function DicomIcon({ size = 18, color = "currentColor" }) {
|
|
|
1348
1452
|
}
|
|
1349
1453
|
);
|
|
1350
1454
|
}
|
|
1351
|
-
function
|
|
1352
|
-
return /* @__PURE__ */ jsxRuntime.
|
|
1455
|
+
function PeopleIcon({ size = 18, color = "currentColor" }) {
|
|
1456
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1353
1457
|
"svg",
|
|
1354
1458
|
{
|
|
1355
1459
|
xmlns: "http://www.w3.org/2000/svg",
|
|
1356
1460
|
width: size,
|
|
1357
1461
|
height: size,
|
|
1358
1462
|
viewBox: "0 0 24 24",
|
|
1359
|
-
fill:
|
|
1463
|
+
fill: "none",
|
|
1464
|
+
stroke: color,
|
|
1465
|
+
strokeWidth: "2",
|
|
1466
|
+
strokeLinecap: "round",
|
|
1467
|
+
strokeLinejoin: "round",
|
|
1360
1468
|
"aria-hidden": "true",
|
|
1361
|
-
children:
|
|
1469
|
+
children: [
|
|
1470
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2" }),
|
|
1471
|
+
/* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "9", cy: "7", r: "4" }),
|
|
1472
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M22 21v-2a4 4 0 0 0-3-3.87" }),
|
|
1473
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M16 3.13a4 4 0 0 1 0 7.75" })
|
|
1474
|
+
]
|
|
1362
1475
|
}
|
|
1363
1476
|
);
|
|
1364
1477
|
}
|
|
@@ -1383,6 +1496,7 @@ function resolveDisplayName(patientData, displayName) {
|
|
|
1383
1496
|
}
|
|
1384
1497
|
function PatientHeader({
|
|
1385
1498
|
patientData,
|
|
1499
|
+
participants,
|
|
1386
1500
|
onOpenDicom,
|
|
1387
1501
|
onOpenSettings,
|
|
1388
1502
|
onBack,
|
|
@@ -1448,15 +1562,18 @@ function PatientHeader({
|
|
|
1448
1562
|
]
|
|
1449
1563
|
}
|
|
1450
1564
|
),
|
|
1451
|
-
onOpenSettings && /* @__PURE__ */ jsxRuntime.
|
|
1565
|
+
onOpenSettings && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1452
1566
|
"button",
|
|
1453
1567
|
{
|
|
1454
1568
|
onClick: onOpenSettings,
|
|
1455
1569
|
style: styles.settingsIconButton,
|
|
1456
1570
|
type: "button",
|
|
1457
|
-
"aria-label":
|
|
1458
|
-
title: "
|
|
1459
|
-
children:
|
|
1571
|
+
"aria-label": `Open channel settings (${participants.length} participants)`,
|
|
1572
|
+
title: "Channel participants",
|
|
1573
|
+
children: [
|
|
1574
|
+
/* @__PURE__ */ jsxRuntime.jsx(PeopleIcon, { size: 18, color: COLOR.neutral600 }),
|
|
1575
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { style: styles.participantCount, children: participants.length })
|
|
1576
|
+
]
|
|
1460
1577
|
}
|
|
1461
1578
|
)
|
|
1462
1579
|
] })
|
|
@@ -1564,20 +1681,28 @@ var styles = {
|
|
|
1564
1681
|
borderRadius: RADIUS.lg,
|
|
1565
1682
|
cursor: "pointer"
|
|
1566
1683
|
},
|
|
1567
|
-
//
|
|
1568
|
-
//
|
|
1684
|
+
// Channel-info button: people icon + participant count. Click opens the
|
|
1685
|
+
// channel settings overlay (kept on the same handler as before so hosts
|
|
1686
|
+
// don't have to re-wire — the affordance just looks like a "members"
|
|
1687
|
+
// pill now instead of a gear).
|
|
1569
1688
|
settingsIconButton: {
|
|
1570
|
-
|
|
1571
|
-
height: SIZE.control,
|
|
1689
|
+
minHeight: SIZE.control,
|
|
1572
1690
|
display: "inline-flex",
|
|
1573
1691
|
alignItems: "center",
|
|
1574
|
-
|
|
1692
|
+
gap: SPACE.S2,
|
|
1693
|
+
padding: `0 ${SPACE.S3}`,
|
|
1575
1694
|
backgroundColor: COLOR.white,
|
|
1576
1695
|
border: `1px solid ${COLOR.neutral200}`,
|
|
1577
1696
|
borderRadius: RADIUS.lg,
|
|
1578
1697
|
color: COLOR.neutral600,
|
|
1579
1698
|
cursor: "pointer",
|
|
1580
1699
|
flexShrink: 0
|
|
1700
|
+
},
|
|
1701
|
+
participantCount: {
|
|
1702
|
+
fontSize: FONT_SIZE.sm,
|
|
1703
|
+
fontWeight: FONT_WEIGHT.semibold,
|
|
1704
|
+
color: COLOR.neutral700,
|
|
1705
|
+
fontVariantNumeric: "tabular-nums"
|
|
1581
1706
|
}
|
|
1582
1707
|
};
|
|
1583
1708
|
function ReplyQuoteBlock({
|
|
@@ -3998,6 +4123,20 @@ var styles12 = {
|
|
|
3998
4123
|
}
|
|
3999
4124
|
};
|
|
4000
4125
|
ensureGlobalStyles();
|
|
4126
|
+
var DARK_THEME_OVERRIDES = {
|
|
4127
|
+
["--natoe-colab-white"]: "#0b0b0c",
|
|
4128
|
+
["--natoe-colab-neutral-50"]: "#18181c",
|
|
4129
|
+
["--natoe-colab-neutral-100"]: "#1f1f23",
|
|
4130
|
+
["--natoe-colab-neutral-200"]: "#2a2a2e",
|
|
4131
|
+
["--natoe-colab-neutral-300"]: "#3f3f44",
|
|
4132
|
+
["--natoe-colab-neutral-400"]: "#6b7280",
|
|
4133
|
+
["--natoe-colab-neutral-500"]: "#9ca3af",
|
|
4134
|
+
["--natoe-colab-neutral-600"]: "#cbd5e1",
|
|
4135
|
+
["--natoe-colab-neutral-700"]: "#e5e7eb",
|
|
4136
|
+
["--natoe-colab-neutral-800"]: "#f3f4f6",
|
|
4137
|
+
["--natoe-colab-neutral-900"]: "#ffffff",
|
|
4138
|
+
["--natoe-colab-primary-bg"]: "rgba(37, 99, 235, 0.18)"
|
|
4139
|
+
};
|
|
4001
4140
|
function CollabPanel({
|
|
4002
4141
|
orderId,
|
|
4003
4142
|
patientData,
|
|
@@ -4006,6 +4145,7 @@ function CollabPanel({
|
|
|
4006
4145
|
onBack,
|
|
4007
4146
|
hidePatientName = false,
|
|
4008
4147
|
onConversationChange,
|
|
4148
|
+
themeMode = "light",
|
|
4009
4149
|
className,
|
|
4010
4150
|
style
|
|
4011
4151
|
}) {
|
|
@@ -4081,16 +4221,21 @@ function CollabPanel({
|
|
|
4081
4221
|
}
|
|
4082
4222
|
};
|
|
4083
4223
|
const pinDisabled = pinnedMessages.length >= MAX_PINNED_MESSAGES;
|
|
4224
|
+
const containerStyle = {
|
|
4225
|
+
...panelStyles.container,
|
|
4226
|
+
...themeMode === "dark" ? DARK_THEME_OVERRIDES : {},
|
|
4227
|
+
...style
|
|
4228
|
+
};
|
|
4084
4229
|
if (error) {
|
|
4085
|
-
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: [ROOT_CLASS, className].filter(Boolean).join(" "), style:
|
|
4230
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: [ROOT_CLASS, className].filter(Boolean).join(" "), style: containerStyle, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { style: panelStyles.errorState, children: [
|
|
4086
4231
|
/* @__PURE__ */ jsxRuntime.jsx("p", { style: panelStyles.errorTitle, children: "Unable to load conversation" }),
|
|
4087
4232
|
/* @__PURE__ */ jsxRuntime.jsx("p", { style: panelStyles.errorMessage, children: error })
|
|
4088
4233
|
] }) });
|
|
4089
4234
|
}
|
|
4090
4235
|
if (isLoading && !conversation) {
|
|
4091
|
-
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: [ROOT_CLASS, className].filter(Boolean).join(" "), style:
|
|
4236
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: [ROOT_CLASS, className].filter(Boolean).join(" "), style: containerStyle, children: /* @__PURE__ */ jsxRuntime.jsx("div", { style: panelStyles.loadingState, children: "Loading conversation..." }) });
|
|
4092
4237
|
}
|
|
4093
|
-
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className
|
|
4238
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: [ROOT_CLASS, className].filter(Boolean).join(" "), style: containerStyle, children: [
|
|
4094
4239
|
showSettings && conversation ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
4095
4240
|
ChannelSettings,
|
|
4096
4241
|
{
|
|
@@ -4526,6 +4671,7 @@ function useInlineCollab({
|
|
|
4526
4671
|
const elementRef = React4.useRef(null);
|
|
4527
4672
|
const observerRef = React4.useRef(null);
|
|
4528
4673
|
const subscribedConversationIdRef = React4.useRef(null);
|
|
4674
|
+
const channelSubscriptionRef = React4.useRef(null);
|
|
4529
4675
|
const trimToLimit = React4.useCallback(
|
|
4530
4676
|
(msgs) => msgs.length > messageLimit ? msgs.slice(msgs.length - messageLimit) : msgs,
|
|
4531
4677
|
[messageLimit]
|
|
@@ -4563,7 +4709,9 @@ function useInlineCollab({
|
|
|
4563
4709
|
const subscribe = React4.useCallback(
|
|
4564
4710
|
(conversationId) => {
|
|
4565
4711
|
if (subscribedConversationIdRef.current === conversationId) return;
|
|
4566
|
-
|
|
4712
|
+
channelSubscriptionRef.current?.release();
|
|
4713
|
+
channelSubscriptionRef.current = null;
|
|
4714
|
+
const subscription = socket.joinConversation(conversationId, {
|
|
4567
4715
|
onMessage: (msg) => {
|
|
4568
4716
|
setMessages((prev) => trimToLimit([...prev, msg]));
|
|
4569
4717
|
if (msg.senderId !== config.userId) {
|
|
@@ -4579,19 +4727,18 @@ function useInlineCollab({
|
|
|
4579
4727
|
setParticipants((prev) => prev.filter((p) => p.userId !== participant.userId));
|
|
4580
4728
|
}
|
|
4581
4729
|
});
|
|
4730
|
+
channelSubscriptionRef.current = subscription;
|
|
4582
4731
|
subscribedConversationIdRef.current = conversationId;
|
|
4583
4732
|
setIsSubscribed(true);
|
|
4584
4733
|
},
|
|
4585
4734
|
[socket, trimToLimit, config.userId]
|
|
4586
4735
|
);
|
|
4587
4736
|
const unsubscribe = React4.useCallback(() => {
|
|
4588
|
-
|
|
4589
|
-
|
|
4590
|
-
|
|
4591
|
-
|
|
4592
|
-
|
|
4593
|
-
}
|
|
4594
|
-
}, [socket]);
|
|
4737
|
+
channelSubscriptionRef.current?.release();
|
|
4738
|
+
channelSubscriptionRef.current = null;
|
|
4739
|
+
subscribedConversationIdRef.current = null;
|
|
4740
|
+
setIsSubscribed(false);
|
|
4741
|
+
}, []);
|
|
4595
4742
|
const containerRef = React4.useCallback(
|
|
4596
4743
|
(element) => {
|
|
4597
4744
|
if (observerRef.current) {
|
|
@@ -4733,16 +4880,52 @@ function useInlineCollab({
|
|
|
4733
4880
|
};
|
|
4734
4881
|
}
|
|
4735
4882
|
ensureGlobalStyles();
|
|
4883
|
+
function palette(mode) {
|
|
4884
|
+
if (mode === "dark") {
|
|
4885
|
+
return {
|
|
4886
|
+
bg: "transparent",
|
|
4887
|
+
border: "transparent",
|
|
4888
|
+
previewBorder: "#1f1f23",
|
|
4889
|
+
senderName: "#f3f4f6",
|
|
4890
|
+
messageText: "#cbd5e1",
|
|
4891
|
+
systemText: "#6b7280",
|
|
4892
|
+
inputBg: "#18181c",
|
|
4893
|
+
inputBorder: "#2a2a2e",
|
|
4894
|
+
inputText: "#e5e7eb",
|
|
4895
|
+
audioBg: "rgba(37, 99, 235, 0.18)",
|
|
4896
|
+
audioFg: "#93c5fd",
|
|
4897
|
+
expandBorder: "#2a2a2e",
|
|
4898
|
+
expandColor: "#9ca3af"
|
|
4899
|
+
};
|
|
4900
|
+
}
|
|
4901
|
+
return {
|
|
4902
|
+
bg: COLOR.white,
|
|
4903
|
+
border: COLOR.neutral200,
|
|
4904
|
+
previewBorder: COLOR.neutral100,
|
|
4905
|
+
senderName: COLOR.neutral700,
|
|
4906
|
+
messageText: COLOR.neutral600,
|
|
4907
|
+
systemText: COLOR.neutral400,
|
|
4908
|
+
inputBg: "transparent",
|
|
4909
|
+
inputBorder: COLOR.neutral200,
|
|
4910
|
+
inputText: COLOR.neutral900,
|
|
4911
|
+
audioBg: COLOR.primaryBg,
|
|
4912
|
+
audioFg: COLOR.primary,
|
|
4913
|
+
expandBorder: COLOR.neutral200,
|
|
4914
|
+
expandColor: COLOR.neutral500
|
|
4915
|
+
};
|
|
4916
|
+
}
|
|
4736
4917
|
function CollabInline({
|
|
4737
4918
|
orderId,
|
|
4738
4919
|
patientData,
|
|
4739
4920
|
participantIds,
|
|
4740
4921
|
onExpand,
|
|
4741
|
-
messageLimit =
|
|
4922
|
+
messageLimit = 1,
|
|
4742
4923
|
placeholder = "Type a message about this case...",
|
|
4924
|
+
mode = "light",
|
|
4743
4925
|
className,
|
|
4744
4926
|
style
|
|
4745
4927
|
}) {
|
|
4928
|
+
const pal = palette(mode);
|
|
4746
4929
|
const {
|
|
4747
4930
|
hasConversation,
|
|
4748
4931
|
messages,
|
|
@@ -4754,11 +4937,17 @@ function CollabInline({
|
|
|
4754
4937
|
sendAudioMessage,
|
|
4755
4938
|
containerRef
|
|
4756
4939
|
} = useInlineCollab({ orderId, patientData, participantIds, messageLimit });
|
|
4940
|
+
const containerStyle = {
|
|
4941
|
+
...styles14.container,
|
|
4942
|
+
backgroundColor: pal.bg,
|
|
4943
|
+
borderColor: pal.border,
|
|
4944
|
+
...style
|
|
4945
|
+
};
|
|
4757
4946
|
if (isLoading && !hasConversation) {
|
|
4758
|
-
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: [ROOT_CLASS, className].filter(Boolean).join(" "), style:
|
|
4947
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: [ROOT_CLASS, className].filter(Boolean).join(" "), style: containerStyle, children: /* @__PURE__ */ jsxRuntime.jsx("div", { style: styles14.loadingState, children: "Loading..." }) });
|
|
4759
4948
|
}
|
|
4760
|
-
return /* @__PURE__ */ jsxRuntime.jsxs("div", { ref: containerRef, className: [ROOT_CLASS, className].filter(Boolean).join(" "), style:
|
|
4761
|
-
hasConversation && messages.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { style: styles14.preview, children: messages.slice(-messageLimit).map((message) => /* @__PURE__ */ jsxRuntime.jsx(InlineMessageRow, { message }, message.id)) }),
|
|
4949
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { ref: containerRef, className: [ROOT_CLASS, className].filter(Boolean).join(" "), style: containerStyle, children: [
|
|
4950
|
+
hasConversation && messages.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { style: { ...styles14.preview, borderBottomColor: pal.previewBorder }, children: messages.slice(-messageLimit).map((message) => /* @__PURE__ */ jsxRuntime.jsx(InlineMessageRow, { message, pal }, message.id)) }),
|
|
4762
4951
|
error && /* @__PURE__ */ jsxRuntime.jsx("div", { style: styles14.error, children: error }),
|
|
4763
4952
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
4764
4953
|
InlineInputBar,
|
|
@@ -4769,33 +4958,34 @@ function CollabInline({
|
|
|
4769
4958
|
unreadCount: hasConversation ? unreadCount : 0,
|
|
4770
4959
|
isLive: isSubscribed,
|
|
4771
4960
|
showExpand: hasConversation && !!onExpand,
|
|
4772
|
-
onExpand
|
|
4961
|
+
onExpand,
|
|
4962
|
+
pal
|
|
4773
4963
|
}
|
|
4774
4964
|
)
|
|
4775
4965
|
] });
|
|
4776
4966
|
}
|
|
4777
|
-
function InlineMessageRow({ message }) {
|
|
4967
|
+
function InlineMessageRow({ message, pal }) {
|
|
4778
4968
|
if (message.type === "system") {
|
|
4779
|
-
return /* @__PURE__ */ jsxRuntime.jsx("div", { style: styles14.systemRow, children: /* @__PURE__ */ jsxRuntime.jsx("span", { style: styles14.systemText, children: message.body }) });
|
|
4969
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { style: styles14.systemRow, children: /* @__PURE__ */ jsxRuntime.jsx("span", { style: { ...styles14.systemText, color: pal.systemText }, children: message.body }) });
|
|
4780
4970
|
}
|
|
4781
4971
|
if (message.type === "audio" && message.mediaUrl) {
|
|
4782
4972
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: styles14.messageRow, children: [
|
|
4783
4973
|
/* @__PURE__ */ jsxRuntime.jsx(RoleDot, { role: message.senderRole }),
|
|
4784
|
-
/* @__PURE__ */ jsxRuntime.jsxs("span", { style: styles14.senderName, children: [
|
|
4974
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { style: { ...styles14.senderName, color: pal.senderName }, children: [
|
|
4785
4975
|
message.senderName,
|
|
4786
4976
|
":"
|
|
4787
4977
|
] }),
|
|
4788
|
-
/* @__PURE__ */ jsxRuntime.jsx(InlineAudioPlayer, { url: message.mediaUrl, duration: message.mediaDuration })
|
|
4978
|
+
/* @__PURE__ */ jsxRuntime.jsx(InlineAudioPlayer, { url: message.mediaUrl, duration: message.mediaDuration, pal })
|
|
4789
4979
|
] });
|
|
4790
4980
|
}
|
|
4791
4981
|
const preview = renderMessagePreview(message);
|
|
4792
4982
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: styles14.messageRow, children: [
|
|
4793
4983
|
/* @__PURE__ */ jsxRuntime.jsx(RoleDot, { role: message.senderRole }),
|
|
4794
|
-
/* @__PURE__ */ jsxRuntime.jsxs("span", { style: styles14.senderName, children: [
|
|
4984
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { style: { ...styles14.senderName, color: pal.senderName }, children: [
|
|
4795
4985
|
message.senderName,
|
|
4796
4986
|
":"
|
|
4797
4987
|
] }),
|
|
4798
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { style: styles14.messageText, children: preview })
|
|
4988
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { style: { ...styles14.messageText, color: pal.messageText }, children: preview })
|
|
4799
4989
|
] });
|
|
4800
4990
|
}
|
|
4801
4991
|
function RoleDot({ role }) {
|
|
@@ -4811,7 +5001,11 @@ function RoleDot({ role }) {
|
|
|
4811
5001
|
};
|
|
4812
5002
|
return /* @__PURE__ */ jsxRuntime.jsx("span", { style: { ...styles14.roleDot, backgroundColor: colors[role] } });
|
|
4813
5003
|
}
|
|
4814
|
-
function InlineAudioPlayer({
|
|
5004
|
+
function InlineAudioPlayer({
|
|
5005
|
+
url,
|
|
5006
|
+
duration,
|
|
5007
|
+
pal
|
|
5008
|
+
}) {
|
|
4815
5009
|
const audioRef = React4.useRef(null);
|
|
4816
5010
|
const [isPlaying, setIsPlaying] = React4.useState(false);
|
|
4817
5011
|
const [currentTime, setCurrentTime] = React4.useState(0);
|
|
@@ -4853,7 +5047,7 @@ function InlineAudioPlayer({ url, duration }) {
|
|
|
4853
5047
|
const total = duration ?? 0;
|
|
4854
5048
|
const remaining = Math.max(0, total - Math.floor(currentTime));
|
|
4855
5049
|
const displayTime = isPlaying ? remaining : total;
|
|
4856
|
-
return /* @__PURE__ */ jsxRuntime.jsxs("span", { style: styles14.audioPlayer, children: [
|
|
5050
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("span", { style: { ...styles14.audioPlayer, backgroundColor: pal.audioBg, borderColor: pal.audioBg }, children: [
|
|
4857
5051
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
4858
5052
|
"button",
|
|
4859
5053
|
{
|
|
@@ -4865,15 +5059,15 @@ function InlineAudioPlayer({ url, duration }) {
|
|
|
4865
5059
|
}
|
|
4866
5060
|
),
|
|
4867
5061
|
/* @__PURE__ */ jsxRuntime.jsxs("span", { style: styles14.audioWaveform, children: [
|
|
4868
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { style: { ...styles14.waveBar, height: "40%" } }),
|
|
4869
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { style: { ...styles14.waveBar, height: "80%" } }),
|
|
4870
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { style: { ...styles14.waveBar, height: "60%" } }),
|
|
4871
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { style: { ...styles14.waveBar, height: "90%" } }),
|
|
4872
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { style: { ...styles14.waveBar, height: "50%" } }),
|
|
4873
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { style: { ...styles14.waveBar, height: "70%" } }),
|
|
4874
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { style: { ...styles14.waveBar, height: "40%" } })
|
|
5062
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { style: { ...styles14.waveBar, height: "40%", backgroundColor: pal.audioFg } }),
|
|
5063
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { style: { ...styles14.waveBar, height: "80%", backgroundColor: pal.audioFg } }),
|
|
5064
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { style: { ...styles14.waveBar, height: "60%", backgroundColor: pal.audioFg } }),
|
|
5065
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { style: { ...styles14.waveBar, height: "90%", backgroundColor: pal.audioFg } }),
|
|
5066
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { style: { ...styles14.waveBar, height: "50%", backgroundColor: pal.audioFg } }),
|
|
5067
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { style: { ...styles14.waveBar, height: "70%", backgroundColor: pal.audioFg } }),
|
|
5068
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { style: { ...styles14.waveBar, height: "40%", backgroundColor: pal.audioFg } })
|
|
4875
5069
|
] }),
|
|
4876
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { style: styles14.audioDuration, children: formatDuration2(displayTime) }),
|
|
5070
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { style: { ...styles14.audioDuration, color: pal.audioFg }, children: formatDuration2(displayTime) }),
|
|
4877
5071
|
/* @__PURE__ */ jsxRuntime.jsx("audio", { ref: audioRef, src: url, preload: "metadata" })
|
|
4878
5072
|
] });
|
|
4879
5073
|
}
|
|
@@ -4903,7 +5097,8 @@ function InlineInputBar({
|
|
|
4903
5097
|
unreadCount,
|
|
4904
5098
|
isLive,
|
|
4905
5099
|
showExpand,
|
|
4906
|
-
onExpand
|
|
5100
|
+
onExpand,
|
|
5101
|
+
pal
|
|
4907
5102
|
}) {
|
|
4908
5103
|
const [text, setText] = React4.useState("");
|
|
4909
5104
|
const [isSending, setIsSending] = React4.useState(false);
|
|
@@ -4940,7 +5135,12 @@ function InlineInputBar({
|
|
|
4940
5135
|
onKeyDown: handleKeyDown,
|
|
4941
5136
|
placeholder,
|
|
4942
5137
|
disabled: isSending,
|
|
4943
|
-
style:
|
|
5138
|
+
style: {
|
|
5139
|
+
...styles14.input,
|
|
5140
|
+
backgroundColor: pal.inputBg,
|
|
5141
|
+
borderColor: pal.inputBorder,
|
|
5142
|
+
color: pal.inputText
|
|
5143
|
+
}
|
|
4944
5144
|
}
|
|
4945
5145
|
),
|
|
4946
5146
|
unreadCount > 0 && /* @__PURE__ */ jsxRuntime.jsx("span", { style: styles14.unreadBadge, title: `${unreadCount} unread`, children: unreadCount > 99 ? "99+" : unreadCount }),
|
|
@@ -4960,7 +5160,11 @@ function InlineInputBar({
|
|
|
4960
5160
|
"button",
|
|
4961
5161
|
{
|
|
4962
5162
|
onClick: onExpand,
|
|
4963
|
-
style:
|
|
5163
|
+
style: {
|
|
5164
|
+
...styles14.expandButton,
|
|
5165
|
+
borderColor: pal.expandBorder,
|
|
5166
|
+
color: pal.expandColor
|
|
5167
|
+
},
|
|
4964
5168
|
title: "Expand to full chat",
|
|
4965
5169
|
type: "button",
|
|
4966
5170
|
children: "\u26F6"
|
|
@@ -5507,8 +5711,11 @@ function ConversationList({
|
|
|
5507
5711
|
onSelect,
|
|
5508
5712
|
className
|
|
5509
5713
|
}) {
|
|
5714
|
+
const { totalUnread } = useCollab();
|
|
5510
5715
|
const [query, setQuery] = React4.useState("");
|
|
5511
|
-
const [filter, setFilter] = React4.useState(
|
|
5716
|
+
const [filter, setFilter] = React4.useState(
|
|
5717
|
+
() => totalUnread > 0 ? "unread" : "all"
|
|
5718
|
+
);
|
|
5512
5719
|
const [searchFocused, setSearchFocused] = React4.useState(false);
|
|
5513
5720
|
const unreadCount = React4.useMemo(
|
|
5514
5721
|
() => conversations.filter((c) => c.unreadCount > 0).length,
|
|
@@ -6138,7 +6345,7 @@ function useUnreadCount() {
|
|
|
6138
6345
|
const [counts, setCounts] = React4.useState({});
|
|
6139
6346
|
React4.useEffect(() => {
|
|
6140
6347
|
socket.onUnreadCountUpdate((serverCounts) => {
|
|
6141
|
-
setCounts(serverCounts);
|
|
6348
|
+
setCounts(serverCounts.conversation);
|
|
6142
6349
|
});
|
|
6143
6350
|
}, [socket]);
|
|
6144
6351
|
const getCountForConversation = React4.useCallback(
|