jupyterlab-chat 0.9.0 → 0.10.0

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/lib/factory.d.ts CHANGED
@@ -52,7 +52,7 @@ export declare class ChatWidgetFactory extends ABCWidgetFactory<LabChatPanel, La
52
52
  private _rmRegistry;
53
53
  private _chatCommandRegistry?;
54
54
  private _attachmentOpenerRegistry?;
55
- private _inputToolbarRegistry?;
55
+ private _inputToolbarFactory?;
56
56
  }
57
57
  export declare namespace ChatWidgetFactory {
58
58
  interface IContext extends DocumentRegistry.IContext<LabChatModel> {
package/lib/factory.js CHANGED
@@ -52,9 +52,7 @@ export class ChatWidgetFactory extends ABCWidgetFactory {
52
52
  this._rmRegistry = options.rmRegistry;
53
53
  this._chatCommandRegistry = options.chatCommandRegistry;
54
54
  this._attachmentOpenerRegistry = options.attachmentOpenerRegistry;
55
- if (options.inputToolbarFactory) {
56
- this._inputToolbarRegistry = options.inputToolbarFactory.create();
57
- }
55
+ this._inputToolbarFactory = options.inputToolbarFactory;
58
56
  }
59
57
  /**
60
58
  * Create a new widget given a context.
@@ -67,7 +65,9 @@ export class ChatWidgetFactory extends ABCWidgetFactory {
67
65
  context.themeManager = this._themeManager;
68
66
  context.chatCommandRegistry = this._chatCommandRegistry;
69
67
  context.attachmentOpenerRegistry = this._attachmentOpenerRegistry;
70
- context.inputToolbarRegistry = this._inputToolbarRegistry;
68
+ if (this._inputToolbarFactory) {
69
+ context.inputToolbarRegistry = this._inputToolbarFactory.create();
70
+ }
71
71
  return new LabChatPanel({
72
72
  context,
73
73
  content: new ChatWidget(context)
package/lib/model.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { ChatModel, IChatMessage, IInputModel, INewMessage, IUser } from '@jupyter/chat';
1
+ import { AbstractChatModel, AbstractChatContext, IChatContext, IChatMessage, IChatModel, IInputModel, INewMessage, IUser } from '@jupyter/chat';
2
2
  import { IChangedArgs } from '@jupyterlab/coreutils';
3
3
  import { DocumentRegistry } from '@jupyterlab/docregistry';
4
4
  import { User } from '@jupyterlab/services';
@@ -10,7 +10,7 @@ import { YChat } from './ychat';
10
10
  * Chat model namespace.
11
11
  */
12
12
  export declare namespace LabChatModel {
13
- interface IOptions extends ChatModel.IOptions {
13
+ interface IOptions extends IChatModel.IOptions {
14
14
  widgetConfig: IWidgetConfig;
15
15
  user: User.IIdentity | null;
16
16
  sharedModel?: YChat;
@@ -20,7 +20,7 @@ export declare namespace LabChatModel {
20
20
  /**
21
21
  * The chat model.
22
22
  */
23
- export declare class LabChatModel extends ChatModel implements DocumentRegistry.IModel {
23
+ export declare class LabChatModel extends AbstractChatModel implements DocumentRegistry.IModel {
24
24
  constructor(options: LabChatModel.IOptions);
25
25
  readonly collaborative = true;
26
26
  get user(): IUser;
@@ -38,7 +38,8 @@ export declare class LabChatModel extends ChatModel implements DocumentRegistry.
38
38
  fromString(data: string): void;
39
39
  toJSON(): PartialJSONObject;
40
40
  fromJSON(data: PartialJSONObject): void;
41
- messagesInserted(index: number, messages: IChatMessage[]): void;
41
+ createChatContext(): IChatContext;
42
+ messagesInserted(index: number, messages: IChatMessage[]): Promise<void>;
42
43
  sendMessage(message: INewMessage): Promise<boolean | void> | boolean | void;
43
44
  /**
44
45
  * Override the clear messages method.
@@ -55,6 +56,7 @@ export declare class LabChatModel extends ChatModel implements DocumentRegistry.
55
56
  * Used to populate the writers list.
56
57
  */
57
58
  onAwarenessChange: () => void;
59
+ private _buildMentionList;
58
60
  private _resetWritingStatus;
59
61
  private _onchange;
60
62
  readonly defaultKernelName: string;
@@ -69,3 +71,12 @@ export declare class LabChatModel extends ChatModel implements DocumentRegistry.
69
71
  private _timeoutWriting;
70
72
  private _user;
71
73
  }
74
+ /**
75
+ * The chat context to be sent to the input model.
76
+ */
77
+ export declare class LabChatContext extends AbstractChatContext {
78
+ /**
79
+ * The list of users who have connected to this chat.
80
+ */
81
+ get users(): IUser[];
82
+ }
package/lib/model.js CHANGED
@@ -2,7 +2,7 @@
2
2
  * Copyright (c) Jupyter Development Team.
3
3
  * Distributed under the terms of the Modified BSD License.
4
4
  */
5
- import { ChatModel } from '@jupyter/chat';
5
+ import { AbstractChatModel, AbstractChatContext } from '@jupyter/chat';
6
6
  import { PromiseDelegate, UUID } from '@lumino/coreutils';
7
7
  import { Signal } from '@lumino/signaling';
8
8
  import { YChat } from './ychat';
@@ -10,7 +10,7 @@ const WRITING_DELAY = 1000;
10
10
  /**
11
11
  * The chat model.
12
12
  */
13
- export class LabChatModel extends ChatModel {
13
+ export class LabChatModel extends AbstractChatModel {
14
14
  constructor(options) {
15
15
  super(options);
16
16
  this.collaborative = true;
@@ -48,17 +48,17 @@ export class LabChatModel extends ChatModel {
48
48
  }
49
49
  this.updateWriters(writers);
50
50
  };
51
- this._onchange = (_, changes) => {
51
+ this._onchange = async (_, changes) => {
52
52
  if (changes.messageChanges) {
53
53
  const msgDelta = changes.messageChanges;
54
54
  let index = 0;
55
- msgDelta.forEach(delta => {
55
+ for (const delta of msgDelta) {
56
56
  if (delta.retain) {
57
57
  index += delta.retain;
58
58
  }
59
59
  else if (delta.insert) {
60
60
  const messages = delta.insert.map(ymessage => {
61
- const { sender, attachments: attachmentIds, ...baseMessage } = ymessage;
61
+ const { sender, attachments: attachmentIds, mentions: mentionsIds, ...baseMessage } = ymessage;
62
62
  // Build the base message with sender.
63
63
  const msg = {
64
64
  ...baseMessage,
@@ -79,15 +79,21 @@ export class LabChatModel extends ChatModel {
79
79
  msg.attachments = attachments;
80
80
  }
81
81
  }
82
+ const mentions = mentionsIds === null || mentionsIds === void 0 ? void 0 : mentionsIds.map(user => this.sharedModel.getUser(user) || {
83
+ username: 'User undefined'
84
+ });
85
+ if (mentions === null || mentions === void 0 ? void 0 : mentions.length) {
86
+ msg.mentions = mentions;
87
+ }
82
88
  return msg;
83
89
  });
84
- this.messagesInserted(index, messages);
90
+ await this.messagesInserted(index, messages);
85
91
  index += messages.length;
86
92
  }
87
93
  else if (delta.delete) {
88
94
  this.messagesDeleted(index, delta.delete);
89
95
  }
90
- });
96
+ }
91
97
  }
92
98
  if (changes.metadataChanges) {
93
99
  changes.metadataChanges.forEach(change => {
@@ -98,6 +104,14 @@ export class LabChatModel extends ChatModel {
98
104
  }
99
105
  });
100
106
  }
107
+ if (changes.userChanges) {
108
+ // Update the current user if it changes (if it has been mentioned for example).
109
+ changes.userChanges.forEach(change => {
110
+ if (change.key === this._user.username && change.newValue) {
111
+ this._user = change.newValue;
112
+ }
113
+ });
114
+ }
101
115
  };
102
116
  this.defaultKernelName = '';
103
117
  this.defaultKernelLanguage = '';
@@ -178,14 +192,18 @@ export class LabChatModel extends ChatModel {
178
192
  fromJSON(data) {
179
193
  // nothing to do
180
194
  }
181
- messagesInserted(index, messages) {
195
+ createChatContext() {
196
+ return new LabChatContext({ model: this });
197
+ }
198
+ async messagesInserted(index, messages) {
182
199
  // Ensure the chat has an ID before inserting the messages, to properly catch the
183
200
  // unread messages (the last read message is saved using the chat ID).
184
- this._ready.promise.then(() => {
201
+ return this._ready.promise.then(() => {
185
202
  super.messagesInserted(index, messages);
186
203
  });
187
204
  }
188
205
  sendMessage(message) {
206
+ var _a;
189
207
  this._resetWritingStatus();
190
208
  if (this._timeoutWriting !== null) {
191
209
  window.clearTimeout(this._timeoutWriting);
@@ -203,11 +221,17 @@ export class LabChatModel extends ChatModel {
203
221
  this.sharedModel.setUser(this._user);
204
222
  }
205
223
  // Add the attachments to the message.
206
- if (this.input.attachments.length) {
207
- const attachmentIds = this.input.attachments.map(attachment => this.sharedModel.setAttachment(attachment));
224
+ const attachmentIds = (_a = this.input.attachments) === null || _a === void 0 ? void 0 : _a.map(attachment => this.sharedModel.setAttachment(attachment));
225
+ if (attachmentIds === null || attachmentIds === void 0 ? void 0 : attachmentIds.length) {
208
226
  msg.attachments = attachmentIds;
209
- this.input.clearAttachments();
210
227
  }
228
+ this.input.clearAttachments();
229
+ // Add the mentioned users.
230
+ const mentions = this._buildMentionList(this.input.mentions, message.body);
231
+ if (mentions.length) {
232
+ msg.mentions = mentions;
233
+ }
234
+ this.input.clearMentions();
211
235
  this.sharedModel.addMessage(msg);
212
236
  }
213
237
  /**
@@ -235,10 +259,22 @@ export class LabChatModel extends ChatModel {
235
259
  edited: true
236
260
  };
237
261
  }
262
+ // Update the attachments.
238
263
  const attachmentIds = (_a = updatedMessage.attachments) === null || _a === void 0 ? void 0 : _a.map(attachment => this.sharedModel.setAttachment(attachment));
239
- if (attachmentIds) {
264
+ if (attachmentIds === null || attachmentIds === void 0 ? void 0 : attachmentIds.length) {
240
265
  message.attachments = attachmentIds;
241
266
  }
267
+ else {
268
+ delete message.attachments;
269
+ }
270
+ // Update the mentioned users.
271
+ const mentions = this._buildMentionList(updatedMessage.mentions, updatedMessage.body);
272
+ if (mentions.length) {
273
+ message.mentions = mentions;
274
+ }
275
+ else {
276
+ delete message.mentions;
277
+ }
242
278
  this.sharedModel.updateMessage(index, message);
243
279
  }
244
280
  deleteMessage(id) {
@@ -252,6 +288,28 @@ export class LabChatModel extends ChatModel {
252
288
  message.deleted = true;
253
289
  this.sharedModel.updateMessage(index, message);
254
290
  }
291
+ _buildMentionList(userMentions, body) {
292
+ if (!userMentions) {
293
+ return [];
294
+ }
295
+ const mentions = [];
296
+ userMentions.forEach(user => {
297
+ // Make sure the user is still mentioned.
298
+ if (!user.mention_name) {
299
+ return;
300
+ }
301
+ const regex = new RegExp(user.mention_name);
302
+ if (!regex.exec(body)) {
303
+ return;
304
+ }
305
+ // Save the mention name if necessary.
306
+ if (!(this.sharedModel.getUser(user.username) === user)) {
307
+ this.sharedModel.setUser(user);
308
+ }
309
+ mentions.push(user.username);
310
+ });
311
+ return mentions;
312
+ }
255
313
  _resetWritingStatus() {
256
314
  const awareness = this.sharedModel.awareness;
257
315
  const states = awareness.getLocalState();
@@ -260,3 +318,28 @@ export class LabChatModel extends ChatModel {
260
318
  this._timeoutWriting = null;
261
319
  }
262
320
  }
321
+ /**
322
+ * The chat context to be sent to the input model.
323
+ */
324
+ export class LabChatContext extends AbstractChatContext {
325
+ /**
326
+ * The list of users who have connected to this chat.
327
+ */
328
+ get users() {
329
+ const model = this._model;
330
+ const users = new Set();
331
+ // Get the user list from the shared YChat
332
+ Object.values(model.sharedModel.users).forEach(userObject => {
333
+ users.add(userObject);
334
+ });
335
+ // Add the users connected to the chat (even if they never sent a message).
336
+ model.sharedModel.awareness.getStates().forEach(value => {
337
+ const user = value.user;
338
+ if (!user) {
339
+ return;
340
+ }
341
+ users.add(user);
342
+ });
343
+ return Array.from(users);
344
+ }
345
+ }
package/lib/ychat.d.ts CHANGED
@@ -65,7 +65,7 @@ export declare class YChat extends YDocument<IChatChanges> {
65
65
  */
66
66
  static create(options?: YDocument.IOptions): YChat;
67
67
  get id(): string;
68
- get users(): JSONObject;
68
+ get users(): Record<string, IUser>;
69
69
  get messages(): string[];
70
70
  get attachments(): JSONObject;
71
71
  getSource(): JSONObject;
package/lib/ychat.js CHANGED
@@ -140,7 +140,8 @@ export class YChat extends YDocument {
140
140
  return this._metadata.get('id') || '';
141
141
  }
142
142
  get users() {
143
- return JSONExt.deepCopy(this._users.toJSON());
143
+ const userDict = JSONExt.deepCopy(this._users.toJSON());
144
+ return userDict;
144
145
  }
145
146
  get messages() {
146
147
  return JSONExt.deepCopy(this._messages.toJSON());
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jupyterlab-chat",
3
- "version": "0.9.0",
3
+ "version": "0.10.0",
4
4
  "description": "The library to build a chat based on shared document",
5
5
  "keywords": [
6
6
  "jupyter",
@@ -42,7 +42,7 @@
42
42
  "watch:src": "tsc -w --sourceMap"
43
43
  },
44
44
  "dependencies": {
45
- "@jupyter/chat": "^0.9.0",
45
+ "@jupyter/chat": "^0.10.0",
46
46
  "@jupyter/collaborative-drive": "^3.0.0",
47
47
  "@jupyter/ydoc": "^2.0.0 || ^3.0.0",
48
48
  "@jupyterlab/application": "^4.2.0",
package/src/factory.ts CHANGED
@@ -83,9 +83,7 @@ export class ChatWidgetFactory extends ABCWidgetFactory<
83
83
  this._rmRegistry = options.rmRegistry;
84
84
  this._chatCommandRegistry = options.chatCommandRegistry;
85
85
  this._attachmentOpenerRegistry = options.attachmentOpenerRegistry;
86
- if (options.inputToolbarFactory) {
87
- this._inputToolbarRegistry = options.inputToolbarFactory.create();
88
- }
86
+ this._inputToolbarFactory = options.inputToolbarFactory;
89
87
  }
90
88
 
91
89
  /**
@@ -99,7 +97,9 @@ export class ChatWidgetFactory extends ABCWidgetFactory<
99
97
  context.themeManager = this._themeManager;
100
98
  context.chatCommandRegistry = this._chatCommandRegistry;
101
99
  context.attachmentOpenerRegistry = this._attachmentOpenerRegistry;
102
- context.inputToolbarRegistry = this._inputToolbarRegistry;
100
+ if (this._inputToolbarFactory) {
101
+ context.inputToolbarRegistry = this._inputToolbarFactory.create();
102
+ }
103
103
  return new LabChatPanel({
104
104
  context,
105
105
  content: new ChatWidget(context)
@@ -110,7 +110,7 @@ export class ChatWidgetFactory extends ABCWidgetFactory<
110
110
  private _rmRegistry: IRenderMimeRegistry;
111
111
  private _chatCommandRegistry?: IChatCommandRegistry;
112
112
  private _attachmentOpenerRegistry?: IAttachmentOpenerRegistry;
113
- private _inputToolbarRegistry?: IInputToolbarRegistry;
113
+ private _inputToolbarFactory?: IInputToolbarRegistryFactory;
114
114
  }
115
115
 
116
116
  export namespace ChatWidgetFactory {
package/src/model.ts CHANGED
@@ -4,9 +4,12 @@
4
4
  */
5
5
 
6
6
  import {
7
- ChatModel,
7
+ AbstractChatModel,
8
+ AbstractChatContext,
8
9
  IAttachment,
10
+ IChatContext,
9
11
  IChatMessage,
12
+ IChatModel,
10
13
  IInputModel,
11
14
  INewMessage,
12
15
  IUser
@@ -26,7 +29,7 @@ const WRITING_DELAY = 1000;
26
29
  * Chat model namespace.
27
30
  */
28
31
  export namespace LabChatModel {
29
- export interface IOptions extends ChatModel.IOptions {
32
+ export interface IOptions extends IChatModel.IOptions {
30
33
  widgetConfig: IWidgetConfig;
31
34
  user: User.IIdentity | null;
32
35
  sharedModel?: YChat;
@@ -37,7 +40,10 @@ export namespace LabChatModel {
37
40
  /**
38
41
  * The chat model.
39
42
  */
40
- export class LabChatModel extends ChatModel implements DocumentRegistry.IModel {
43
+ export class LabChatModel
44
+ extends AbstractChatModel
45
+ implements DocumentRegistry.IModel
46
+ {
41
47
  constructor(options: LabChatModel.IOptions) {
42
48
  super(options);
43
49
 
@@ -133,10 +139,17 @@ export class LabChatModel extends ChatModel implements DocumentRegistry.IModel {
133
139
  // nothing to do
134
140
  }
135
141
 
136
- messagesInserted(index: number, messages: IChatMessage[]): void {
142
+ createChatContext(): IChatContext {
143
+ return new LabChatContext({ model: this });
144
+ }
145
+
146
+ async messagesInserted(
147
+ index: number,
148
+ messages: IChatMessage[]
149
+ ): Promise<void> {
137
150
  // Ensure the chat has an ID before inserting the messages, to properly catch the
138
151
  // unread messages (the last read message is saved using the chat ID).
139
- this._ready.promise.then(() => {
152
+ return this._ready.promise.then(() => {
140
153
  super.messagesInserted(index, messages);
141
154
  });
142
155
  }
@@ -162,13 +175,20 @@ export class LabChatModel extends ChatModel implements DocumentRegistry.IModel {
162
175
  }
163
176
 
164
177
  // Add the attachments to the message.
165
- if (this.input.attachments.length) {
166
- const attachmentIds = this.input.attachments.map(attachment =>
167
- this.sharedModel.setAttachment(attachment)
168
- );
178
+ const attachmentIds = this.input.attachments?.map(attachment =>
179
+ this.sharedModel.setAttachment(attachment)
180
+ );
181
+ if (attachmentIds?.length) {
169
182
  msg.attachments = attachmentIds;
170
- this.input.clearAttachments();
171
183
  }
184
+ this.input.clearAttachments();
185
+
186
+ // Add the mentioned users.
187
+ const mentions = this._buildMentionList(this.input.mentions, message.body);
188
+ if (mentions.length) {
189
+ msg.mentions = mentions;
190
+ }
191
+ this.input.clearMentions();
172
192
 
173
193
  this.sharedModel.addMessage(msg);
174
194
  }
@@ -201,12 +221,28 @@ export class LabChatModel extends ChatModel implements DocumentRegistry.IModel {
201
221
  edited: true
202
222
  };
203
223
  }
224
+
225
+ // Update the attachments.
204
226
  const attachmentIds = updatedMessage.attachments?.map(attachment =>
205
227
  this.sharedModel.setAttachment(attachment)
206
228
  );
207
- if (attachmentIds) {
229
+ if (attachmentIds?.length) {
208
230
  message.attachments = attachmentIds;
231
+ } else {
232
+ delete message.attachments;
233
+ }
234
+
235
+ // Update the mentioned users.
236
+ const mentions = this._buildMentionList(
237
+ updatedMessage.mentions,
238
+ updatedMessage.body
239
+ );
240
+ if (mentions.length) {
241
+ message.mentions = mentions;
242
+ } else {
243
+ delete message.mentions;
209
244
  }
245
+
210
246
  this.sharedModel.updateMessage(index, message as IYmessage);
211
247
  }
212
248
 
@@ -258,6 +294,33 @@ export class LabChatModel extends ChatModel implements DocumentRegistry.IModel {
258
294
  this.updateWriters(writers);
259
295
  };
260
296
 
297
+ private _buildMentionList(
298
+ userMentions: IUser[] | undefined,
299
+ body: string
300
+ ): string[] {
301
+ if (!userMentions) {
302
+ return [];
303
+ }
304
+ const mentions: string[] = [];
305
+ userMentions.forEach(user => {
306
+ // Make sure the user is still mentioned.
307
+ if (!user.mention_name) {
308
+ return;
309
+ }
310
+ const regex = new RegExp(user.mention_name);
311
+ if (!regex.exec(body)) {
312
+ return;
313
+ }
314
+
315
+ // Save the mention name if necessary.
316
+ if (!(this.sharedModel.getUser(user.username) === user)) {
317
+ this.sharedModel.setUser(user);
318
+ }
319
+ mentions.push(user.username);
320
+ });
321
+ return mentions;
322
+ }
323
+
261
324
  private _resetWritingStatus() {
262
325
  const awareness = this.sharedModel.awareness;
263
326
  const states = awareness.getLocalState();
@@ -266,11 +329,11 @@ export class LabChatModel extends ChatModel implements DocumentRegistry.IModel {
266
329
  this._timeoutWriting = null;
267
330
  }
268
331
 
269
- private _onchange = (_: YChat, changes: IChatChanges) => {
332
+ private _onchange = async (_: YChat, changes: IChatChanges) => {
270
333
  if (changes.messageChanges) {
271
334
  const msgDelta = changes.messageChanges;
272
335
  let index = 0;
273
- msgDelta.forEach(delta => {
336
+ for (const delta of msgDelta) {
274
337
  if (delta.retain) {
275
338
  index += delta.retain;
276
339
  } else if (delta.insert) {
@@ -278,6 +341,7 @@ export class LabChatModel extends ChatModel implements DocumentRegistry.IModel {
278
341
  const {
279
342
  sender,
280
343
  attachments: attachmentIds,
344
+ mentions: mentionsIds,
281
345
  ...baseMessage
282
346
  } = ymessage;
283
347
 
@@ -303,14 +367,24 @@ export class LabChatModel extends ChatModel implements DocumentRegistry.IModel {
303
367
  }
304
368
  }
305
369
 
370
+ const mentions = mentionsIds?.map(
371
+ user =>
372
+ this.sharedModel.getUser(user) || {
373
+ username: 'User undefined'
374
+ }
375
+ );
376
+
377
+ if (mentions?.length) {
378
+ msg.mentions = mentions;
379
+ }
306
380
  return msg;
307
381
  });
308
- this.messagesInserted(index, messages);
382
+ await this.messagesInserted(index, messages);
309
383
  index += messages.length;
310
384
  } else if (delta.delete) {
311
385
  this.messagesDeleted(index, delta.delete);
312
386
  }
313
- });
387
+ }
314
388
  }
315
389
 
316
390
  if (changes.metadataChanges) {
@@ -322,6 +396,15 @@ export class LabChatModel extends ChatModel implements DocumentRegistry.IModel {
322
396
  }
323
397
  });
324
398
  }
399
+
400
+ if (changes.userChanges) {
401
+ // Update the current user if it changes (if it has been mentioned for example).
402
+ changes.userChanges.forEach(change => {
403
+ if (change.key === this._user.username && change.newValue) {
404
+ this._user = change.newValue;
405
+ }
406
+ });
407
+ }
325
408
  };
326
409
 
327
410
  readonly defaultKernelName: string = '';
@@ -339,3 +422,32 @@ export class LabChatModel extends ChatModel implements DocumentRegistry.IModel {
339
422
 
340
423
  private _user: IUser;
341
424
  }
425
+
426
+ /**
427
+ * The chat context to be sent to the input model.
428
+ */
429
+ export class LabChatContext extends AbstractChatContext {
430
+ /**
431
+ * The list of users who have connected to this chat.
432
+ */
433
+ get users(): IUser[] {
434
+ const model = this._model as LabChatModel;
435
+ const users = new Set<IUser>();
436
+
437
+ // Get the user list from the shared YChat
438
+ Object.values(model.sharedModel.users).forEach(userObject => {
439
+ users.add(userObject);
440
+ });
441
+
442
+ // Add the users connected to the chat (even if they never sent a message).
443
+ model.sharedModel.awareness.getStates().forEach(value => {
444
+ const user = value.user as IUser;
445
+ if (!user) {
446
+ return;
447
+ }
448
+ users.add(user);
449
+ });
450
+
451
+ return Array.from(users);
452
+ }
453
+ }
package/src/ychat.ts CHANGED
@@ -100,8 +100,13 @@ export class YChat extends YDocument<IChatChanges> {
100
100
  return (this._metadata.get('id') as string) || '';
101
101
  }
102
102
 
103
- get users(): JSONObject {
104
- return JSONExt.deepCopy(this._users.toJSON());
103
+ get users(): Record<string, IUser> {
104
+ const userDict = JSONExt.deepCopy(this._users.toJSON()) as Record<
105
+ string,
106
+ IUser
107
+ >;
108
+
109
+ return userDict;
105
110
  }
106
111
 
107
112
  get messages(): string[] {