@natoe/colab 0.1.13 → 0.1.14
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 +95 -12
- package/dist/index.d.ts +95 -12
- package/dist/index.js +295 -106
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +295 -106
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -66,6 +66,13 @@ function toCamelKey(key) {
|
|
|
66
66
|
var CollabSocket = class {
|
|
67
67
|
constructor() {
|
|
68
68
|
this.socket = null;
|
|
69
|
+
/**
|
|
70
|
+
* Conversation channels keyed by id. Each entry is reference-counted so
|
|
71
|
+
* multiple surfaces (e.g. inline chat + expanded panel mounted at once
|
|
72
|
+
* for the same conversation) can coexist without one's `leaveConversation`
|
|
73
|
+
* tearing the channel out from under the other. See `joinConversation`
|
|
74
|
+
* and the returned `ChannelSubscription.release`.
|
|
75
|
+
*/
|
|
69
76
|
this.channels = /* @__PURE__ */ new Map();
|
|
70
77
|
this.presences = /* @__PURE__ */ new Map();
|
|
71
78
|
this.userChannel = null;
|
|
@@ -136,59 +143,85 @@ var CollabSocket = class {
|
|
|
136
143
|
onUnreadCountUpdate(callback) {
|
|
137
144
|
this.onUnreadUpdate = callback;
|
|
138
145
|
}
|
|
139
|
-
/**
|
|
146
|
+
/**
|
|
147
|
+
* Join a conversation channel and subscribe to events.
|
|
148
|
+
*
|
|
149
|
+
* Reference-counted: multiple callers can join the same conversation
|
|
150
|
+
* (e.g. inline preview + expanded panel mounted side-by-side). Each
|
|
151
|
+
* call binds its own listeners and gets back a `ChannelSubscription`.
|
|
152
|
+
* The underlying channel only `.leave()`s the server when the LAST
|
|
153
|
+
* subscriber calls `release()`.
|
|
154
|
+
*/
|
|
140
155
|
joinConversation(conversationId, callbacks) {
|
|
141
156
|
if (!this.socket) return null;
|
|
142
|
-
|
|
143
|
-
|
|
157
|
+
let entry = this.channels.get(conversationId);
|
|
158
|
+
if (!entry) {
|
|
159
|
+
const channel2 = this.socket.channel(`conversation:${conversationId}`, {});
|
|
160
|
+
channel2.join().receive("ok", () => {
|
|
161
|
+
}).receive("error", (reason) => {
|
|
162
|
+
this.config?.onError?.({
|
|
163
|
+
code: "CHANNEL_JOIN_ERROR",
|
|
164
|
+
message: `Failed to join conversation ${conversationId}`,
|
|
165
|
+
details: reason
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
entry = { channel: channel2, subscribers: 0, presence: null };
|
|
169
|
+
this.channels.set(conversationId, entry);
|
|
144
170
|
}
|
|
145
|
-
const channel =
|
|
171
|
+
const channel = entry.channel;
|
|
172
|
+
const refs = [];
|
|
173
|
+
const bind = (event, fn) => {
|
|
174
|
+
const ref = channel.on(event, fn);
|
|
175
|
+
refs.push({ event, ref });
|
|
176
|
+
};
|
|
146
177
|
if (callbacks.onMessage) {
|
|
147
|
-
|
|
178
|
+
bind(EVENTS.MESSAGE_NEW, (payload) => {
|
|
148
179
|
callbacks.onMessage(snakeToCamel(payload));
|
|
149
180
|
});
|
|
150
181
|
}
|
|
151
182
|
if (callbacks.onTyping) {
|
|
152
|
-
|
|
183
|
+
bind(EVENTS.USER_TYPING, (payload) => {
|
|
153
184
|
callbacks.onTyping(snakeToCamel(payload));
|
|
154
185
|
});
|
|
155
186
|
}
|
|
156
187
|
if (callbacks.onUserJoined) {
|
|
157
|
-
|
|
188
|
+
bind(EVENTS.USER_JOINED, (payload) => {
|
|
158
189
|
callbacks.onUserJoined(snakeToCamel(payload));
|
|
159
190
|
});
|
|
160
191
|
}
|
|
161
192
|
if (callbacks.onUserLeft) {
|
|
162
|
-
|
|
193
|
+
bind(EVENTS.USER_LEFT, (payload) => {
|
|
163
194
|
callbacks.onUserLeft(snakeToCamel(payload));
|
|
164
195
|
});
|
|
165
196
|
}
|
|
166
197
|
if (callbacks.onChannelUpdated) {
|
|
167
|
-
|
|
198
|
+
bind(EVENTS.CHANNEL_UPDATED, (payload) => {
|
|
168
199
|
callbacks.onChannelUpdated(snakeToCamel(payload));
|
|
169
200
|
});
|
|
170
201
|
}
|
|
171
202
|
if (callbacks.onChannelDeleted) {
|
|
172
|
-
|
|
203
|
+
bind(EVENTS.CHANNEL_DELETED, () => {
|
|
173
204
|
callbacks.onChannelDeleted();
|
|
174
205
|
});
|
|
175
206
|
}
|
|
176
207
|
if (callbacks.onMessageRead) {
|
|
177
|
-
|
|
178
|
-
callbacks.onMessageRead(
|
|
208
|
+
bind(EVENTS.MESSAGE_READ, (payload) => {
|
|
209
|
+
callbacks.onMessageRead(
|
|
210
|
+
snakeToCamel(payload)
|
|
211
|
+
);
|
|
179
212
|
});
|
|
180
213
|
}
|
|
181
214
|
if (callbacks.onMessagePinned) {
|
|
182
|
-
|
|
215
|
+
bind(EVENTS.MESSAGE_PINNED, (payload) => {
|
|
183
216
|
callbacks.onMessagePinned(snakeToCamel(payload));
|
|
184
217
|
});
|
|
185
218
|
}
|
|
186
219
|
if (callbacks.onMessageUnpinned) {
|
|
187
|
-
|
|
220
|
+
bind(EVENTS.MESSAGE_UNPINNED, (payload) => {
|
|
188
221
|
callbacks.onMessageUnpinned(snakeToCamel(payload));
|
|
189
222
|
});
|
|
190
223
|
}
|
|
191
|
-
if (callbacks.onPresence) {
|
|
224
|
+
if (callbacks.onPresence && !entry.presence) {
|
|
192
225
|
const presence = new phoenix.Presence(channel);
|
|
193
226
|
presence.onSync(() => {
|
|
194
227
|
const online = {};
|
|
@@ -197,27 +230,45 @@ var CollabSocket = class {
|
|
|
197
230
|
});
|
|
198
231
|
callbacks.onPresence(online);
|
|
199
232
|
});
|
|
233
|
+
entry.presence = presence;
|
|
200
234
|
this.presences.set(conversationId, presence);
|
|
201
235
|
}
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
236
|
+
entry.subscribers += 1;
|
|
237
|
+
let released = false;
|
|
238
|
+
const release = () => {
|
|
239
|
+
if (released) return;
|
|
240
|
+
released = true;
|
|
241
|
+
const current = this.channels.get(conversationId);
|
|
242
|
+
if (!current) return;
|
|
243
|
+
for (const { event, ref } of refs) {
|
|
244
|
+
current.channel.off(event, ref);
|
|
245
|
+
}
|
|
246
|
+
current.subscribers -= 1;
|
|
247
|
+
if (current.subscribers <= 0) {
|
|
248
|
+
current.channel.leave();
|
|
249
|
+
this.channels.delete(conversationId);
|
|
250
|
+
this.presences.delete(conversationId);
|
|
251
|
+
}
|
|
252
|
+
};
|
|
253
|
+
return { channel, release };
|
|
212
254
|
}
|
|
213
|
-
/**
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
255
|
+
/**
|
|
256
|
+
* @deprecated Use the `release()` method returned by `joinConversation()`.
|
|
257
|
+
* Kept as a no-op so older callers don't throw — but it cannot identify
|
|
258
|
+
* which subscriber should leave, so it silently does nothing. Any code
|
|
259
|
+
* still calling this will leak listeners and prevent the channel from
|
|
260
|
+
* ever being torn down. Migrate to the subscription handle.
|
|
261
|
+
*/
|
|
262
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
263
|
+
leaveConversation(_conversationId) {
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* Look up the underlying Phoenix Channel for a conversation, if any
|
|
267
|
+
* subscriber is still holding it. All send/push paths go through this
|
|
268
|
+
* helper so the refcounted entry shape is contained to joinConversation.
|
|
269
|
+
*/
|
|
270
|
+
getChannel(conversationId) {
|
|
271
|
+
return this.channels.get(conversationId)?.channel ?? null;
|
|
221
272
|
}
|
|
222
273
|
/** Send a message to a conversation.
|
|
223
274
|
*
|
|
@@ -229,7 +280,7 @@ var CollabSocket = class {
|
|
|
229
280
|
*/
|
|
230
281
|
sendMessage(conversationId, payload) {
|
|
231
282
|
return new Promise((resolve, reject) => {
|
|
232
|
-
const channel = this.
|
|
283
|
+
const channel = this.getChannel(conversationId);
|
|
233
284
|
if (!channel) {
|
|
234
285
|
reject({ code: "NOT_JOINED", message: "Not joined to this conversation" });
|
|
235
286
|
return;
|
|
@@ -252,7 +303,7 @@ var CollabSocket = class {
|
|
|
252
303
|
}
|
|
253
304
|
/** Broadcast typing indicator */
|
|
254
305
|
sendTyping(conversationId, isTyping) {
|
|
255
|
-
const channel = this.
|
|
306
|
+
const channel = this.getChannel(conversationId);
|
|
256
307
|
channel?.push(EVENTS.USER_TYPING, {
|
|
257
308
|
userId: this.config?.userId,
|
|
258
309
|
userName: this.config?.userName,
|
|
@@ -261,7 +312,7 @@ var CollabSocket = class {
|
|
|
261
312
|
}
|
|
262
313
|
/** Mark messages as read */
|
|
263
314
|
markAsRead(conversationId, messageId) {
|
|
264
|
-
const channel = this.
|
|
315
|
+
const channel = this.getChannel(conversationId);
|
|
265
316
|
channel?.push(EVENTS.MESSAGE_READ, {
|
|
266
317
|
messageId,
|
|
267
318
|
userId: this.config?.userId
|
|
@@ -295,7 +346,7 @@ var CollabSocket = class {
|
|
|
295
346
|
/** Generic push with promise wrapper */
|
|
296
347
|
channelPush(conversationId, event, payload) {
|
|
297
348
|
return new Promise((resolve, reject) => {
|
|
298
|
-
const channel = this.
|
|
349
|
+
const channel = this.getChannel(conversationId);
|
|
299
350
|
if (!channel) {
|
|
300
351
|
reject({ code: "NOT_JOINED", message: "Not joined to this conversation" });
|
|
301
352
|
return;
|
|
@@ -312,7 +363,7 @@ var CollabSocket = class {
|
|
|
312
363
|
}
|
|
313
364
|
/** Disconnect socket and leave all channels */
|
|
314
365
|
disconnect() {
|
|
315
|
-
this.channels.forEach((
|
|
366
|
+
this.channels.forEach((entry) => entry.channel.leave());
|
|
316
367
|
this.channels.clear();
|
|
317
368
|
this.presences.clear();
|
|
318
369
|
this.userChannel?.leave();
|
|
@@ -367,7 +418,21 @@ var THEME_VAR = {
|
|
|
367
418
|
dangerBg: "--natoe-colab-danger-bg",
|
|
368
419
|
dangerBorder: "--natoe-colab-danger-border",
|
|
369
420
|
dangerFg: "--natoe-colab-danger-fg",
|
|
370
|
-
fontStack: "--natoe-colab-font-stack"
|
|
421
|
+
fontStack: "--natoe-colab-font-stack",
|
|
422
|
+
// Neutral surface palette — promoted to CSS vars so consumers (e.g.
|
|
423
|
+
// CollabPanel themeMode='dark') can flip the whole grayscale at a subtree
|
|
424
|
+
// level without re-themeing every component.
|
|
425
|
+
white: "--natoe-colab-white",
|
|
426
|
+
neutral50: "--natoe-colab-neutral-50",
|
|
427
|
+
neutral100: "--natoe-colab-neutral-100",
|
|
428
|
+
neutral200: "--natoe-colab-neutral-200",
|
|
429
|
+
neutral300: "--natoe-colab-neutral-300",
|
|
430
|
+
neutral400: "--natoe-colab-neutral-400",
|
|
431
|
+
neutral500: "--natoe-colab-neutral-500",
|
|
432
|
+
neutral600: "--natoe-colab-neutral-600",
|
|
433
|
+
neutral700: "--natoe-colab-neutral-700",
|
|
434
|
+
neutral800: "--natoe-colab-neutral-800",
|
|
435
|
+
neutral900: "--natoe-colab-neutral-900"
|
|
371
436
|
};
|
|
372
437
|
var THEME_DEFAULTS = {
|
|
373
438
|
primary: "#2563eb",
|
|
@@ -380,18 +445,9 @@ var THEME_DEFAULTS = {
|
|
|
380
445
|
dangerBg: "#fef2f2",
|
|
381
446
|
dangerBorder: "#fecaca",
|
|
382
447
|
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.
|
|
448
|
+
fontStack: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif',
|
|
449
|
+
// Neutral grayscale — these defaults are the same hex literals the
|
|
450
|
+
// package shipped with; they're now overridable per-subtree.
|
|
395
451
|
white: "#ffffff",
|
|
396
452
|
neutral50: "#f9fafb",
|
|
397
453
|
neutral100: "#f3f4f6",
|
|
@@ -402,7 +458,32 @@ var COLOR = {
|
|
|
402
458
|
neutral600: "#4b5563",
|
|
403
459
|
neutral700: "#374151",
|
|
404
460
|
neutral800: "#1f2937",
|
|
405
|
-
neutral900: "#111827"
|
|
461
|
+
neutral900: "#111827"
|
|
462
|
+
};
|
|
463
|
+
var cssVar = (name, fallback) => `var(${name}, ${fallback})`;
|
|
464
|
+
var COLOR = {
|
|
465
|
+
// Brand — themeable
|
|
466
|
+
primary: cssVar(THEME_VAR.primary, THEME_DEFAULTS.primary),
|
|
467
|
+
primaryHover: cssVar(THEME_VAR.primaryHover, THEME_DEFAULTS.primaryHover),
|
|
468
|
+
/** Tinted surface for chips/cards on brand-coloured states. */
|
|
469
|
+
primaryBg: cssVar(THEME_VAR.primaryBg, THEME_DEFAULTS.primaryBg),
|
|
470
|
+
primaryFg: cssVar(THEME_VAR.primaryFg, THEME_DEFAULTS.primaryFg),
|
|
471
|
+
// Neutral grayscale — themeable via CSS variables. Light-mode defaults
|
|
472
|
+
// taken from Tailwind's zinc-leaning slate to match the package's
|
|
473
|
+
// existing tonal balance; a host can override the entire palette by
|
|
474
|
+
// setting --natoe-colab-* values on any ancestor element (used by
|
|
475
|
+
// CollabPanel themeMode='dark' for the viewer's left-panel surface).
|
|
476
|
+
white: cssVar(THEME_VAR.white, THEME_DEFAULTS.white),
|
|
477
|
+
neutral50: cssVar(THEME_VAR.neutral50, THEME_DEFAULTS.neutral50),
|
|
478
|
+
neutral100: cssVar(THEME_VAR.neutral100, THEME_DEFAULTS.neutral100),
|
|
479
|
+
neutral200: cssVar(THEME_VAR.neutral200, THEME_DEFAULTS.neutral200),
|
|
480
|
+
neutral300: cssVar(THEME_VAR.neutral300, THEME_DEFAULTS.neutral300),
|
|
481
|
+
neutral400: cssVar(THEME_VAR.neutral400, THEME_DEFAULTS.neutral400),
|
|
482
|
+
neutral500: cssVar(THEME_VAR.neutral500, THEME_DEFAULTS.neutral500),
|
|
483
|
+
neutral600: cssVar(THEME_VAR.neutral600, THEME_DEFAULTS.neutral600),
|
|
484
|
+
neutral700: cssVar(THEME_VAR.neutral700, THEME_DEFAULTS.neutral700),
|
|
485
|
+
neutral800: cssVar(THEME_VAR.neutral800, THEME_DEFAULTS.neutral800),
|
|
486
|
+
neutral900: cssVar(THEME_VAR.neutral900, THEME_DEFAULTS.neutral900),
|
|
406
487
|
slate800: "#1e293b",
|
|
407
488
|
// Semantic — themeable
|
|
408
489
|
success: cssVar(THEME_VAR.success, THEME_DEFAULTS.success),
|
|
@@ -781,6 +862,7 @@ function useConversation({
|
|
|
781
862
|
const [isConnected, setIsConnected] = React4.useState(false);
|
|
782
863
|
const [replyTo, setReplyTo] = React4.useState(null);
|
|
783
864
|
const joinedConversationId = React4.useRef(null);
|
|
865
|
+
const channelSubscription = React4.useRef(null);
|
|
784
866
|
const typingTimers = React4.useRef(/* @__PURE__ */ new Map());
|
|
785
867
|
const ensureConversationInFlight = React4.useRef(null);
|
|
786
868
|
const markedReadIdsRef = React4.useRef(/* @__PURE__ */ new Set());
|
|
@@ -794,7 +876,9 @@ function useConversation({
|
|
|
794
876
|
const joinChannel = React4.useCallback(
|
|
795
877
|
(conv) => {
|
|
796
878
|
if (joinedConversationId.current === conv.id) return;
|
|
797
|
-
|
|
879
|
+
channelSubscription.current?.release();
|
|
880
|
+
channelSubscription.current = null;
|
|
881
|
+
const subscription = socket.joinConversation(conv.id, {
|
|
798
882
|
// Dedupe by id so an optimistic message (added client-side on send)
|
|
799
883
|
// doesn't double up when the server's broadcast arrives.
|
|
800
884
|
onMessage: (msg) => setMessages((prev) => {
|
|
@@ -844,6 +928,8 @@ function useConversation({
|
|
|
844
928
|
onChannelDeleted: () => {
|
|
845
929
|
setConversation(null);
|
|
846
930
|
setMessages([]);
|
|
931
|
+
channelSubscription.current?.release();
|
|
932
|
+
channelSubscription.current = null;
|
|
847
933
|
joinedConversationId.current = null;
|
|
848
934
|
setIsConnected(false);
|
|
849
935
|
},
|
|
@@ -869,6 +955,7 @@ function useConversation({
|
|
|
869
955
|
);
|
|
870
956
|
}
|
|
871
957
|
});
|
|
958
|
+
channelSubscription.current = subscription;
|
|
872
959
|
joinedConversationId.current = conv.id;
|
|
873
960
|
setIsConnected(true);
|
|
874
961
|
},
|
|
@@ -929,10 +1016,9 @@ function useConversation({
|
|
|
929
1016
|
init();
|
|
930
1017
|
return () => {
|
|
931
1018
|
cancelled = true;
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
}
|
|
1019
|
+
channelSubscription.current?.release();
|
|
1020
|
+
channelSubscription.current = null;
|
|
1021
|
+
joinedConversationId.current = null;
|
|
936
1022
|
typingTimers.current.forEach((timer) => clearTimeout(timer));
|
|
937
1023
|
typingTimers.current.clear();
|
|
938
1024
|
markedReadIdsRef.current.clear();
|
|
@@ -1348,17 +1434,26 @@ function DicomIcon({ size = 18, color = "currentColor" }) {
|
|
|
1348
1434
|
}
|
|
1349
1435
|
);
|
|
1350
1436
|
}
|
|
1351
|
-
function
|
|
1352
|
-
return /* @__PURE__ */ jsxRuntime.
|
|
1437
|
+
function PeopleIcon({ size = 18, color = "currentColor" }) {
|
|
1438
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1353
1439
|
"svg",
|
|
1354
1440
|
{
|
|
1355
1441
|
xmlns: "http://www.w3.org/2000/svg",
|
|
1356
1442
|
width: size,
|
|
1357
1443
|
height: size,
|
|
1358
1444
|
viewBox: "0 0 24 24",
|
|
1359
|
-
fill:
|
|
1445
|
+
fill: "none",
|
|
1446
|
+
stroke: color,
|
|
1447
|
+
strokeWidth: "2",
|
|
1448
|
+
strokeLinecap: "round",
|
|
1449
|
+
strokeLinejoin: "round",
|
|
1360
1450
|
"aria-hidden": "true",
|
|
1361
|
-
children:
|
|
1451
|
+
children: [
|
|
1452
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2" }),
|
|
1453
|
+
/* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "9", cy: "7", r: "4" }),
|
|
1454
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M22 21v-2a4 4 0 0 0-3-3.87" }),
|
|
1455
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M16 3.13a4 4 0 0 1 0 7.75" })
|
|
1456
|
+
]
|
|
1362
1457
|
}
|
|
1363
1458
|
);
|
|
1364
1459
|
}
|
|
@@ -1383,6 +1478,7 @@ function resolveDisplayName(patientData, displayName) {
|
|
|
1383
1478
|
}
|
|
1384
1479
|
function PatientHeader({
|
|
1385
1480
|
patientData,
|
|
1481
|
+
participants,
|
|
1386
1482
|
onOpenDicom,
|
|
1387
1483
|
onOpenSettings,
|
|
1388
1484
|
onBack,
|
|
@@ -1448,15 +1544,18 @@ function PatientHeader({
|
|
|
1448
1544
|
]
|
|
1449
1545
|
}
|
|
1450
1546
|
),
|
|
1451
|
-
onOpenSettings && /* @__PURE__ */ jsxRuntime.
|
|
1547
|
+
onOpenSettings && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1452
1548
|
"button",
|
|
1453
1549
|
{
|
|
1454
1550
|
onClick: onOpenSettings,
|
|
1455
1551
|
style: styles.settingsIconButton,
|
|
1456
1552
|
type: "button",
|
|
1457
|
-
"aria-label":
|
|
1458
|
-
title: "
|
|
1459
|
-
children:
|
|
1553
|
+
"aria-label": `Open channel settings (${participants.length} participants)`,
|
|
1554
|
+
title: "Channel participants",
|
|
1555
|
+
children: [
|
|
1556
|
+
/* @__PURE__ */ jsxRuntime.jsx(PeopleIcon, { size: 18, color: COLOR.neutral600 }),
|
|
1557
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { style: styles.participantCount, children: participants.length })
|
|
1558
|
+
]
|
|
1460
1559
|
}
|
|
1461
1560
|
)
|
|
1462
1561
|
] })
|
|
@@ -1564,20 +1663,28 @@ var styles = {
|
|
|
1564
1663
|
borderRadius: RADIUS.lg,
|
|
1565
1664
|
cursor: "pointer"
|
|
1566
1665
|
},
|
|
1567
|
-
//
|
|
1568
|
-
//
|
|
1666
|
+
// Channel-info button: people icon + participant count. Click opens the
|
|
1667
|
+
// channel settings overlay (kept on the same handler as before so hosts
|
|
1668
|
+
// don't have to re-wire — the affordance just looks like a "members"
|
|
1669
|
+
// pill now instead of a gear).
|
|
1569
1670
|
settingsIconButton: {
|
|
1570
|
-
|
|
1571
|
-
height: SIZE.control,
|
|
1671
|
+
minHeight: SIZE.control,
|
|
1572
1672
|
display: "inline-flex",
|
|
1573
1673
|
alignItems: "center",
|
|
1574
|
-
|
|
1674
|
+
gap: SPACE.S2,
|
|
1675
|
+
padding: `0 ${SPACE.S3}`,
|
|
1575
1676
|
backgroundColor: COLOR.white,
|
|
1576
1677
|
border: `1px solid ${COLOR.neutral200}`,
|
|
1577
1678
|
borderRadius: RADIUS.lg,
|
|
1578
1679
|
color: COLOR.neutral600,
|
|
1579
1680
|
cursor: "pointer",
|
|
1580
1681
|
flexShrink: 0
|
|
1682
|
+
},
|
|
1683
|
+
participantCount: {
|
|
1684
|
+
fontSize: FONT_SIZE.sm,
|
|
1685
|
+
fontWeight: FONT_WEIGHT.semibold,
|
|
1686
|
+
color: COLOR.neutral700,
|
|
1687
|
+
fontVariantNumeric: "tabular-nums"
|
|
1581
1688
|
}
|
|
1582
1689
|
};
|
|
1583
1690
|
function ReplyQuoteBlock({
|
|
@@ -3998,6 +4105,20 @@ var styles12 = {
|
|
|
3998
4105
|
}
|
|
3999
4106
|
};
|
|
4000
4107
|
ensureGlobalStyles();
|
|
4108
|
+
var DARK_THEME_OVERRIDES = {
|
|
4109
|
+
["--natoe-colab-white"]: "#0b0b0c",
|
|
4110
|
+
["--natoe-colab-neutral-50"]: "#18181c",
|
|
4111
|
+
["--natoe-colab-neutral-100"]: "#1f1f23",
|
|
4112
|
+
["--natoe-colab-neutral-200"]: "#2a2a2e",
|
|
4113
|
+
["--natoe-colab-neutral-300"]: "#3f3f44",
|
|
4114
|
+
["--natoe-colab-neutral-400"]: "#6b7280",
|
|
4115
|
+
["--natoe-colab-neutral-500"]: "#9ca3af",
|
|
4116
|
+
["--natoe-colab-neutral-600"]: "#cbd5e1",
|
|
4117
|
+
["--natoe-colab-neutral-700"]: "#e5e7eb",
|
|
4118
|
+
["--natoe-colab-neutral-800"]: "#f3f4f6",
|
|
4119
|
+
["--natoe-colab-neutral-900"]: "#ffffff",
|
|
4120
|
+
["--natoe-colab-primary-bg"]: "rgba(37, 99, 235, 0.18)"
|
|
4121
|
+
};
|
|
4001
4122
|
function CollabPanel({
|
|
4002
4123
|
orderId,
|
|
4003
4124
|
patientData,
|
|
@@ -4006,6 +4127,7 @@ function CollabPanel({
|
|
|
4006
4127
|
onBack,
|
|
4007
4128
|
hidePatientName = false,
|
|
4008
4129
|
onConversationChange,
|
|
4130
|
+
themeMode = "light",
|
|
4009
4131
|
className,
|
|
4010
4132
|
style
|
|
4011
4133
|
}) {
|
|
@@ -4081,16 +4203,21 @@ function CollabPanel({
|
|
|
4081
4203
|
}
|
|
4082
4204
|
};
|
|
4083
4205
|
const pinDisabled = pinnedMessages.length >= MAX_PINNED_MESSAGES;
|
|
4206
|
+
const containerStyle = {
|
|
4207
|
+
...panelStyles.container,
|
|
4208
|
+
...themeMode === "dark" ? DARK_THEME_OVERRIDES : {},
|
|
4209
|
+
...style
|
|
4210
|
+
};
|
|
4084
4211
|
if (error) {
|
|
4085
|
-
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: [ROOT_CLASS, className].filter(Boolean).join(" "), style:
|
|
4212
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: [ROOT_CLASS, className].filter(Boolean).join(" "), style: containerStyle, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { style: panelStyles.errorState, children: [
|
|
4086
4213
|
/* @__PURE__ */ jsxRuntime.jsx("p", { style: panelStyles.errorTitle, children: "Unable to load conversation" }),
|
|
4087
4214
|
/* @__PURE__ */ jsxRuntime.jsx("p", { style: panelStyles.errorMessage, children: error })
|
|
4088
4215
|
] }) });
|
|
4089
4216
|
}
|
|
4090
4217
|
if (isLoading && !conversation) {
|
|
4091
|
-
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: [ROOT_CLASS, className].filter(Boolean).join(" "), style:
|
|
4218
|
+
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
4219
|
}
|
|
4093
|
-
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className
|
|
4220
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: [ROOT_CLASS, className].filter(Boolean).join(" "), style: containerStyle, children: [
|
|
4094
4221
|
showSettings && conversation ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
4095
4222
|
ChannelSettings,
|
|
4096
4223
|
{
|
|
@@ -4526,6 +4653,7 @@ function useInlineCollab({
|
|
|
4526
4653
|
const elementRef = React4.useRef(null);
|
|
4527
4654
|
const observerRef = React4.useRef(null);
|
|
4528
4655
|
const subscribedConversationIdRef = React4.useRef(null);
|
|
4656
|
+
const channelSubscriptionRef = React4.useRef(null);
|
|
4529
4657
|
const trimToLimit = React4.useCallback(
|
|
4530
4658
|
(msgs) => msgs.length > messageLimit ? msgs.slice(msgs.length - messageLimit) : msgs,
|
|
4531
4659
|
[messageLimit]
|
|
@@ -4563,7 +4691,9 @@ function useInlineCollab({
|
|
|
4563
4691
|
const subscribe = React4.useCallback(
|
|
4564
4692
|
(conversationId) => {
|
|
4565
4693
|
if (subscribedConversationIdRef.current === conversationId) return;
|
|
4566
|
-
|
|
4694
|
+
channelSubscriptionRef.current?.release();
|
|
4695
|
+
channelSubscriptionRef.current = null;
|
|
4696
|
+
const subscription = socket.joinConversation(conversationId, {
|
|
4567
4697
|
onMessage: (msg) => {
|
|
4568
4698
|
setMessages((prev) => trimToLimit([...prev, msg]));
|
|
4569
4699
|
if (msg.senderId !== config.userId) {
|
|
@@ -4579,19 +4709,18 @@ function useInlineCollab({
|
|
|
4579
4709
|
setParticipants((prev) => prev.filter((p) => p.userId !== participant.userId));
|
|
4580
4710
|
}
|
|
4581
4711
|
});
|
|
4712
|
+
channelSubscriptionRef.current = subscription;
|
|
4582
4713
|
subscribedConversationIdRef.current = conversationId;
|
|
4583
4714
|
setIsSubscribed(true);
|
|
4584
4715
|
},
|
|
4585
4716
|
[socket, trimToLimit, config.userId]
|
|
4586
4717
|
);
|
|
4587
4718
|
const unsubscribe = React4.useCallback(() => {
|
|
4588
|
-
|
|
4589
|
-
|
|
4590
|
-
|
|
4591
|
-
|
|
4592
|
-
|
|
4593
|
-
}
|
|
4594
|
-
}, [socket]);
|
|
4719
|
+
channelSubscriptionRef.current?.release();
|
|
4720
|
+
channelSubscriptionRef.current = null;
|
|
4721
|
+
subscribedConversationIdRef.current = null;
|
|
4722
|
+
setIsSubscribed(false);
|
|
4723
|
+
}, []);
|
|
4595
4724
|
const containerRef = React4.useCallback(
|
|
4596
4725
|
(element) => {
|
|
4597
4726
|
if (observerRef.current) {
|
|
@@ -4733,16 +4862,52 @@ function useInlineCollab({
|
|
|
4733
4862
|
};
|
|
4734
4863
|
}
|
|
4735
4864
|
ensureGlobalStyles();
|
|
4865
|
+
function palette(mode) {
|
|
4866
|
+
if (mode === "dark") {
|
|
4867
|
+
return {
|
|
4868
|
+
bg: "transparent",
|
|
4869
|
+
border: "transparent",
|
|
4870
|
+
previewBorder: "#1f1f23",
|
|
4871
|
+
senderName: "#f3f4f6",
|
|
4872
|
+
messageText: "#cbd5e1",
|
|
4873
|
+
systemText: "#6b7280",
|
|
4874
|
+
inputBg: "#18181c",
|
|
4875
|
+
inputBorder: "#2a2a2e",
|
|
4876
|
+
inputText: "#e5e7eb",
|
|
4877
|
+
audioBg: "rgba(37, 99, 235, 0.18)",
|
|
4878
|
+
audioFg: "#93c5fd",
|
|
4879
|
+
expandBorder: "#2a2a2e",
|
|
4880
|
+
expandColor: "#9ca3af"
|
|
4881
|
+
};
|
|
4882
|
+
}
|
|
4883
|
+
return {
|
|
4884
|
+
bg: COLOR.white,
|
|
4885
|
+
border: COLOR.neutral200,
|
|
4886
|
+
previewBorder: COLOR.neutral100,
|
|
4887
|
+
senderName: COLOR.neutral700,
|
|
4888
|
+
messageText: COLOR.neutral600,
|
|
4889
|
+
systemText: COLOR.neutral400,
|
|
4890
|
+
inputBg: "transparent",
|
|
4891
|
+
inputBorder: COLOR.neutral200,
|
|
4892
|
+
inputText: COLOR.neutral900,
|
|
4893
|
+
audioBg: COLOR.primaryBg,
|
|
4894
|
+
audioFg: COLOR.primary,
|
|
4895
|
+
expandBorder: COLOR.neutral200,
|
|
4896
|
+
expandColor: COLOR.neutral500
|
|
4897
|
+
};
|
|
4898
|
+
}
|
|
4736
4899
|
function CollabInline({
|
|
4737
4900
|
orderId,
|
|
4738
4901
|
patientData,
|
|
4739
4902
|
participantIds,
|
|
4740
4903
|
onExpand,
|
|
4741
|
-
messageLimit =
|
|
4904
|
+
messageLimit = 1,
|
|
4742
4905
|
placeholder = "Type a message about this case...",
|
|
4906
|
+
mode = "light",
|
|
4743
4907
|
className,
|
|
4744
4908
|
style
|
|
4745
4909
|
}) {
|
|
4910
|
+
const pal = palette(mode);
|
|
4746
4911
|
const {
|
|
4747
4912
|
hasConversation,
|
|
4748
4913
|
messages,
|
|
@@ -4754,11 +4919,17 @@ function CollabInline({
|
|
|
4754
4919
|
sendAudioMessage,
|
|
4755
4920
|
containerRef
|
|
4756
4921
|
} = useInlineCollab({ orderId, patientData, participantIds, messageLimit });
|
|
4922
|
+
const containerStyle = {
|
|
4923
|
+
...styles14.container,
|
|
4924
|
+
backgroundColor: pal.bg,
|
|
4925
|
+
borderColor: pal.border,
|
|
4926
|
+
...style
|
|
4927
|
+
};
|
|
4757
4928
|
if (isLoading && !hasConversation) {
|
|
4758
|
-
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: [ROOT_CLASS, className].filter(Boolean).join(" "), style:
|
|
4929
|
+
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
4930
|
}
|
|
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)) }),
|
|
4931
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { ref: containerRef, className: [ROOT_CLASS, className].filter(Boolean).join(" "), style: containerStyle, children: [
|
|
4932
|
+
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
4933
|
error && /* @__PURE__ */ jsxRuntime.jsx("div", { style: styles14.error, children: error }),
|
|
4763
4934
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
4764
4935
|
InlineInputBar,
|
|
@@ -4769,33 +4940,34 @@ function CollabInline({
|
|
|
4769
4940
|
unreadCount: hasConversation ? unreadCount : 0,
|
|
4770
4941
|
isLive: isSubscribed,
|
|
4771
4942
|
showExpand: hasConversation && !!onExpand,
|
|
4772
|
-
onExpand
|
|
4943
|
+
onExpand,
|
|
4944
|
+
pal
|
|
4773
4945
|
}
|
|
4774
4946
|
)
|
|
4775
4947
|
] });
|
|
4776
4948
|
}
|
|
4777
|
-
function InlineMessageRow({ message }) {
|
|
4949
|
+
function InlineMessageRow({ message, pal }) {
|
|
4778
4950
|
if (message.type === "system") {
|
|
4779
|
-
return /* @__PURE__ */ jsxRuntime.jsx("div", { style: styles14.systemRow, children: /* @__PURE__ */ jsxRuntime.jsx("span", { style: styles14.systemText, children: message.body }) });
|
|
4951
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { style: styles14.systemRow, children: /* @__PURE__ */ jsxRuntime.jsx("span", { style: { ...styles14.systemText, color: pal.systemText }, children: message.body }) });
|
|
4780
4952
|
}
|
|
4781
4953
|
if (message.type === "audio" && message.mediaUrl) {
|
|
4782
4954
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: styles14.messageRow, children: [
|
|
4783
4955
|
/* @__PURE__ */ jsxRuntime.jsx(RoleDot, { role: message.senderRole }),
|
|
4784
|
-
/* @__PURE__ */ jsxRuntime.jsxs("span", { style: styles14.senderName, children: [
|
|
4956
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { style: { ...styles14.senderName, color: pal.senderName }, children: [
|
|
4785
4957
|
message.senderName,
|
|
4786
4958
|
":"
|
|
4787
4959
|
] }),
|
|
4788
|
-
/* @__PURE__ */ jsxRuntime.jsx(InlineAudioPlayer, { url: message.mediaUrl, duration: message.mediaDuration })
|
|
4960
|
+
/* @__PURE__ */ jsxRuntime.jsx(InlineAudioPlayer, { url: message.mediaUrl, duration: message.mediaDuration, pal })
|
|
4789
4961
|
] });
|
|
4790
4962
|
}
|
|
4791
4963
|
const preview = renderMessagePreview(message);
|
|
4792
4964
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: styles14.messageRow, children: [
|
|
4793
4965
|
/* @__PURE__ */ jsxRuntime.jsx(RoleDot, { role: message.senderRole }),
|
|
4794
|
-
/* @__PURE__ */ jsxRuntime.jsxs("span", { style: styles14.senderName, children: [
|
|
4966
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { style: { ...styles14.senderName, color: pal.senderName }, children: [
|
|
4795
4967
|
message.senderName,
|
|
4796
4968
|
":"
|
|
4797
4969
|
] }),
|
|
4798
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { style: styles14.messageText, children: preview })
|
|
4970
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { style: { ...styles14.messageText, color: pal.messageText }, children: preview })
|
|
4799
4971
|
] });
|
|
4800
4972
|
}
|
|
4801
4973
|
function RoleDot({ role }) {
|
|
@@ -4811,7 +4983,11 @@ function RoleDot({ role }) {
|
|
|
4811
4983
|
};
|
|
4812
4984
|
return /* @__PURE__ */ jsxRuntime.jsx("span", { style: { ...styles14.roleDot, backgroundColor: colors[role] } });
|
|
4813
4985
|
}
|
|
4814
|
-
function InlineAudioPlayer({
|
|
4986
|
+
function InlineAudioPlayer({
|
|
4987
|
+
url,
|
|
4988
|
+
duration,
|
|
4989
|
+
pal
|
|
4990
|
+
}) {
|
|
4815
4991
|
const audioRef = React4.useRef(null);
|
|
4816
4992
|
const [isPlaying, setIsPlaying] = React4.useState(false);
|
|
4817
4993
|
const [currentTime, setCurrentTime] = React4.useState(0);
|
|
@@ -4853,7 +5029,7 @@ function InlineAudioPlayer({ url, duration }) {
|
|
|
4853
5029
|
const total = duration ?? 0;
|
|
4854
5030
|
const remaining = Math.max(0, total - Math.floor(currentTime));
|
|
4855
5031
|
const displayTime = isPlaying ? remaining : total;
|
|
4856
|
-
return /* @__PURE__ */ jsxRuntime.jsxs("span", { style: styles14.audioPlayer, children: [
|
|
5032
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("span", { style: { ...styles14.audioPlayer, backgroundColor: pal.audioBg, borderColor: pal.audioBg }, children: [
|
|
4857
5033
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
4858
5034
|
"button",
|
|
4859
5035
|
{
|
|
@@ -4865,15 +5041,15 @@ function InlineAudioPlayer({ url, duration }) {
|
|
|
4865
5041
|
}
|
|
4866
5042
|
),
|
|
4867
5043
|
/* @__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%" } })
|
|
5044
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { style: { ...styles14.waveBar, height: "40%", backgroundColor: pal.audioFg } }),
|
|
5045
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { style: { ...styles14.waveBar, height: "80%", backgroundColor: pal.audioFg } }),
|
|
5046
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { style: { ...styles14.waveBar, height: "60%", backgroundColor: pal.audioFg } }),
|
|
5047
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { style: { ...styles14.waveBar, height: "90%", backgroundColor: pal.audioFg } }),
|
|
5048
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { style: { ...styles14.waveBar, height: "50%", backgroundColor: pal.audioFg } }),
|
|
5049
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { style: { ...styles14.waveBar, height: "70%", backgroundColor: pal.audioFg } }),
|
|
5050
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { style: { ...styles14.waveBar, height: "40%", backgroundColor: pal.audioFg } })
|
|
4875
5051
|
] }),
|
|
4876
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { style: styles14.audioDuration, children: formatDuration2(displayTime) }),
|
|
5052
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { style: { ...styles14.audioDuration, color: pal.audioFg }, children: formatDuration2(displayTime) }),
|
|
4877
5053
|
/* @__PURE__ */ jsxRuntime.jsx("audio", { ref: audioRef, src: url, preload: "metadata" })
|
|
4878
5054
|
] });
|
|
4879
5055
|
}
|
|
@@ -4903,7 +5079,8 @@ function InlineInputBar({
|
|
|
4903
5079
|
unreadCount,
|
|
4904
5080
|
isLive,
|
|
4905
5081
|
showExpand,
|
|
4906
|
-
onExpand
|
|
5082
|
+
onExpand,
|
|
5083
|
+
pal
|
|
4907
5084
|
}) {
|
|
4908
5085
|
const [text, setText] = React4.useState("");
|
|
4909
5086
|
const [isSending, setIsSending] = React4.useState(false);
|
|
@@ -4940,7 +5117,12 @@ function InlineInputBar({
|
|
|
4940
5117
|
onKeyDown: handleKeyDown,
|
|
4941
5118
|
placeholder,
|
|
4942
5119
|
disabled: isSending,
|
|
4943
|
-
style:
|
|
5120
|
+
style: {
|
|
5121
|
+
...styles14.input,
|
|
5122
|
+
backgroundColor: pal.inputBg,
|
|
5123
|
+
borderColor: pal.inputBorder,
|
|
5124
|
+
color: pal.inputText
|
|
5125
|
+
}
|
|
4944
5126
|
}
|
|
4945
5127
|
),
|
|
4946
5128
|
unreadCount > 0 && /* @__PURE__ */ jsxRuntime.jsx("span", { style: styles14.unreadBadge, title: `${unreadCount} unread`, children: unreadCount > 99 ? "99+" : unreadCount }),
|
|
@@ -4960,7 +5142,11 @@ function InlineInputBar({
|
|
|
4960
5142
|
"button",
|
|
4961
5143
|
{
|
|
4962
5144
|
onClick: onExpand,
|
|
4963
|
-
style:
|
|
5145
|
+
style: {
|
|
5146
|
+
...styles14.expandButton,
|
|
5147
|
+
borderColor: pal.expandBorder,
|
|
5148
|
+
color: pal.expandColor
|
|
5149
|
+
},
|
|
4964
5150
|
title: "Expand to full chat",
|
|
4965
5151
|
type: "button",
|
|
4966
5152
|
children: "\u26F6"
|
|
@@ -5507,8 +5693,11 @@ function ConversationList({
|
|
|
5507
5693
|
onSelect,
|
|
5508
5694
|
className
|
|
5509
5695
|
}) {
|
|
5696
|
+
const { totalUnread } = useCollab();
|
|
5510
5697
|
const [query, setQuery] = React4.useState("");
|
|
5511
|
-
const [filter, setFilter] = React4.useState(
|
|
5698
|
+
const [filter, setFilter] = React4.useState(
|
|
5699
|
+
() => totalUnread > 0 ? "unread" : "all"
|
|
5700
|
+
);
|
|
5512
5701
|
const [searchFocused, setSearchFocused] = React4.useState(false);
|
|
5513
5702
|
const unreadCount = React4.useMemo(
|
|
5514
5703
|
() => conversations.filter((c) => c.unreadCount > 0).length,
|