jupyterlab-chat 0.19.0-alpha.2 → 0.19.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/model.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { AbstractChatModel, AbstractChatContext, IChatContext, IChatMessage, IChatModel, INewMessage, IUser } from '@jupyter/chat';
1
+ import { AbstractChatModel, AbstractChatContext, IChatContext, IChatModel, IMessageContent, 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';
@@ -65,13 +65,13 @@ export declare class LabChatModel extends AbstractChatModel implements DocumentR
65
65
  toJSON(): PartialJSONObject;
66
66
  fromJSON(data: PartialJSONObject): void;
67
67
  createChatContext(): IChatContext;
68
- messagesInserted(index: number, messages: IChatMessage[]): Promise<void>;
68
+ messagesInserted(index: number, messages: IMessageContent[]): Promise<void>;
69
69
  sendMessage(message: INewMessage): Promise<boolean | void> | boolean | void;
70
70
  /**
71
71
  * Override the clear messages method.
72
72
  */
73
73
  clearMessages(): void;
74
- updateMessage(id: string, updatedMessage: IChatMessage): Promise<boolean | void> | boolean | void;
74
+ updateMessage(id: string, updatedMessage: IMessageContent): Promise<boolean | void> | boolean | void;
75
75
  deleteMessage(id: string): Promise<boolean | void> | boolean | void;
76
76
  /**
77
77
  * Function called when the input content changed.
package/lib/model.js CHANGED
@@ -104,8 +104,8 @@ export class LabChatModel extends AbstractChatModel {
104
104
  this.updateWriters(writers);
105
105
  };
106
106
  this._onchange = async (_, changes) => {
107
- if (changes.messageChanges) {
108
- const msgDelta = changes.messageChanges;
107
+ if (changes.messageListChanges) {
108
+ const msgDelta = changes.messageListChanges;
109
109
  let index = 0;
110
110
  for (const delta of msgDelta) {
111
111
  if (delta.retain) {
@@ -113,7 +113,7 @@ export class LabChatModel extends AbstractChatModel {
113
113
  }
114
114
  else if (delta.insert) {
115
115
  const messages = delta.insert.map(ymessage => {
116
- const { sender, attachments: attachmentIds, mentions: mentionsIds, ...baseMessage } = ymessage;
116
+ const { sender, attachments: attachmentIds, mentions: mentionsIds, ...baseMessage } = ymessage.toJSON();
117
117
  // Build the base message with sender.
118
118
  const msg = {
119
119
  ...baseMessage,
@@ -152,6 +152,51 @@ export class LabChatModel extends AbstractChatModel {
152
152
  }
153
153
  }
154
154
  }
155
+ if (changes.messageChanges) {
156
+ // Update change in the message.
157
+ changes.messageChanges.forEach(change => {
158
+ const message = this.messages[change.index];
159
+ if (change.type === 'remove') {
160
+ delete message[change.key];
161
+ }
162
+ else if (change.newValue !== undefined) {
163
+ const key = change.key;
164
+ const value = change.newValue;
165
+ if (key === 'attachments') {
166
+ const attachments = [];
167
+ value.forEach(attachmentId => {
168
+ const attachment = this.sharedModel.getAttachment(attachmentId);
169
+ if (attachment) {
170
+ attachments.push(attachment);
171
+ }
172
+ });
173
+ if (attachments.length) {
174
+ message.update({ attachments });
175
+ }
176
+ else {
177
+ message.update({ attachments: undefined });
178
+ }
179
+ }
180
+ else if (key === 'mentions') {
181
+ const mentions = value.map(user => this.sharedModel.getUser(user) || {
182
+ username: 'User undefined',
183
+ mention_name: 'User-undefined'
184
+ });
185
+ if (mentions === null || mentions === void 0 ? void 0 : mentions.length) {
186
+ message.update({ mentions });
187
+ }
188
+ }
189
+ else if (['body', 'time', 'raw_time', 'deleted', 'edited'].includes(key)) {
190
+ const update = {};
191
+ update[key] = value;
192
+ message.update(update);
193
+ }
194
+ else {
195
+ console.error(`The attribute '${key}' of message cannot be updated`);
196
+ }
197
+ }
198
+ });
199
+ }
155
200
  if (changes.metadataChanges) {
156
201
  changes.metadataChanges.forEach(change => {
157
202
  // no need to search for update or add, if the new value contains ID, let's
package/lib/ychat.d.ts CHANGED
@@ -1,10 +1,11 @@
1
- import { IAttachment, IChatMessage, IUser } from '@jupyter/chat';
1
+ import { IAttachment, IMessageContent, IUser } from '@jupyter/chat';
2
2
  import { Delta, DocumentChange, IMapChange, YDocument } from '@jupyter/ydoc';
3
3
  import { JSONObject, PartialJSONValue } from '@lumino/coreutils';
4
+ import * as Y from 'yjs';
4
5
  /**
5
6
  * The type for a YMessage.
6
7
  */
7
- export type IYmessage = IChatMessage<string, string>;
8
+ export type IYmessage = IMessageContent<string, string>;
8
9
  /**
9
10
  * The type for a YMessage.
10
11
  */
@@ -16,7 +17,11 @@ export interface IChatChanges extends DocumentChange {
16
17
  /**
17
18
  * Changes in messages.
18
19
  */
19
- messageChanges?: MessageChange;
20
+ messageListChanges?: MessageListChange;
21
+ /**
22
+ * Changes of one message.
23
+ */
24
+ messageChanges?: MessageChange[];
20
25
  /**
21
26
  * Changes in users.
22
27
  */
@@ -30,10 +35,16 @@ export interface IChatChanges extends DocumentChange {
30
35
  */
31
36
  metadataChanges?: MetadataChange[];
32
37
  }
38
+ /**
39
+ * The message list change type.
40
+ */
41
+ export type MessageListChange = Delta<Y.Map<any>[]>;
33
42
  /**
34
43
  * The message change type.
35
44
  */
36
- export type MessageChange = Delta<IYmessage[]>;
45
+ export type MessageChange = IMapChange<any> & {
46
+ index: number;
47
+ };
37
48
  /**
38
49
  * The user change type.
39
50
  */
@@ -73,8 +84,8 @@ export declare class YChat extends YDocument<IChatChanges> {
73
84
  getUser(username: string | undefined): IUser | undefined;
74
85
  setUser(user: IUser): void;
75
86
  getMessage(index: number): IYmessage | undefined;
76
- addMessage(value: IYmessage): void;
77
- updateMessage(index: number, value: IYmessage): void;
87
+ addMessage(msg: IYmessage): void;
88
+ updateMessage(index: number, msg: IYmessage): void;
78
89
  getMessageIndex(id: string): number;
79
90
  deleteMessage(index: number): void;
80
91
  getAttachment(id: string): IAttachment | undefined;
package/lib/ychat.js CHANGED
@@ -4,6 +4,7 @@
4
4
  */
5
5
  import { YDocument } from '@jupyter/ydoc';
6
6
  import { JSONExt, UUID } from '@lumino/coreutils';
7
+ import * as Y from 'yjs';
7
8
  /**
8
9
  * The jupyterlab chat shared document.
9
10
  */
@@ -50,10 +51,55 @@ export class YChat extends YDocument {
50
51
  });
51
52
  this._changed.emit({ userChanges });
52
53
  };
53
- this._messagesObserver = (event) => {
54
- const messageChanges = event.delta;
55
- this._changed.emit({
56
- messageChanges: messageChanges
54
+ this._messagesObserver = (events) => {
55
+ events.forEach(event => {
56
+ if (event instanceof Y.YArrayEvent) {
57
+ // Change on the message list.
58
+ const messageListChanges = event.delta;
59
+ this._changed.emit({
60
+ messageListChanges
61
+ });
62
+ }
63
+ else if (event instanceof Y.YMapEvent) {
64
+ // Change on existing message(s), let's update the whole message.
65
+ const messageChanges = new Array();
66
+ event.keysChanged.forEach(key => {
67
+ const change = event.changes.keys.get(key);
68
+ if (change) {
69
+ const index = this.getMessageIndex(event.target.get('id'));
70
+ switch (change.action) {
71
+ case 'add':
72
+ messageChanges.push({
73
+ index,
74
+ key,
75
+ newValue: event.target.get(key),
76
+ type: 'add'
77
+ });
78
+ break;
79
+ case 'delete':
80
+ messageChanges.push({
81
+ index,
82
+ key,
83
+ oldValue: change.oldValue,
84
+ type: 'remove'
85
+ });
86
+ break;
87
+ case 'update':
88
+ messageChanges.push({
89
+ index,
90
+ key: key,
91
+ oldValue: change.oldValue,
92
+ newValue: event.target.get(key),
93
+ type: 'change'
94
+ });
95
+ break;
96
+ }
97
+ }
98
+ });
99
+ this._changed.emit({
100
+ messageChanges
101
+ });
102
+ }
57
103
  });
58
104
  };
59
105
  this._attachmentsObserver = (event) => {
@@ -122,7 +168,7 @@ export class YChat extends YDocument {
122
168
  this._users = this.ydoc.getMap('users');
123
169
  this._users.observe(this._usersObserver);
124
170
  this._messages = this.ydoc.getArray('messages');
125
- this._messages.observe(this._messagesObserver);
171
+ this._messages.observeDeep(this._messagesObserver);
126
172
  this._attachments = this.ydoc.getMap('attachments');
127
173
  this._attachments.observe(this._attachmentsObserver);
128
174
  this._metadata = this.ydoc.getMap('metadata');
@@ -162,9 +208,15 @@ export class YChat extends YDocument {
162
208
  this.transact(() => {
163
209
  var _a, _b, _c;
164
210
  const messages = (_a = value['messages']) !== null && _a !== void 0 ? _a : [];
211
+ const ymessages = [];
165
212
  messages.forEach(message => {
166
- this._messages.push([message]);
213
+ const ymessage = new Y.Map();
214
+ for (const [key, value] of Object.entries(message)) {
215
+ ymessage.set(key, value);
216
+ }
217
+ ymessages.push(ymessage);
167
218
  });
219
+ this._messages.push(ymessages);
168
220
  const users = (_b = value['users']) !== null && _b !== void 0 ? _b : {};
169
221
  Object.entries(users).forEach(([key, val]) => this._users.set(key, val));
170
222
  const metadata = (_c = value['metadata']) !== null && _c !== void 0 ? _c : {};
@@ -187,21 +239,29 @@ export class YChat extends YDocument {
187
239
  });
188
240
  }
189
241
  getMessage(index) {
190
- return this._messages.get(index);
242
+ return this._messages.get(index).toJSON();
191
243
  }
192
- addMessage(value) {
244
+ addMessage(msg) {
193
245
  this.transact(() => {
194
- this._messages.push([value]);
246
+ const ymessage = new Y.Map();
247
+ for (const [key, value] of Object.entries(msg)) {
248
+ ymessage.set(key, value);
249
+ }
250
+ this._messages.push([ymessage]);
195
251
  });
196
252
  }
197
- updateMessage(index, value) {
253
+ updateMessage(index, msg) {
198
254
  this.transact(() => {
199
- this._messages.delete(index);
200
- this._messages.insert(index, [value]);
255
+ const original = this._messages.get(index);
256
+ for (const [key, value] of Object.entries(msg)) {
257
+ if (original.get(key) !== value) {
258
+ original.set(key, value);
259
+ }
260
+ }
201
261
  });
202
262
  }
203
263
  getMessageIndex(id) {
204
- return this._messages.toArray().findIndex(msg => msg.id === id);
264
+ return this._messages.toArray().findIndex(msg => msg.get('id') === id);
205
265
  }
206
266
  deleteMessage(index) {
207
267
  this.transact(() => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jupyterlab-chat",
3
- "version": "0.19.0-alpha.2",
3
+ "version": "0.19.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.19.0-alpha.2",
45
+ "@jupyter/chat": "^0.19.0",
46
46
  "@jupyter/collaborative-drive": "^4.0.2",
47
47
  "@jupyter/ydoc": "^2.0.0 || ^3.0.0",
48
48
  "@jupyterlab/application": "^4.2.0",
package/src/model.ts CHANGED
@@ -8,9 +8,9 @@ import {
8
8
  AbstractChatContext,
9
9
  IAttachment,
10
10
  IChatContext,
11
- IChatMessage,
12
11
  IChatModel,
13
12
  IInputModel,
13
+ IMessageContent,
14
14
  INewMessage,
15
15
  IUser
16
16
  } from '@jupyter/chat';
@@ -189,7 +189,7 @@ export class LabChatModel
189
189
 
190
190
  async messagesInserted(
191
191
  index: number,
192
- messages: IChatMessage[]
192
+ messages: IMessageContent[]
193
193
  ): Promise<void> {
194
194
  // Ensure the chat has an ID before inserting the messages, to properly catch the
195
195
  // unread messages (the last read message is saved using the chat ID).
@@ -246,7 +246,7 @@ export class LabChatModel
246
246
 
247
247
  updateMessage(
248
248
  id: string,
249
- updatedMessage: IChatMessage
249
+ updatedMessage: IMessageContent
250
250
  ): Promise<boolean | void> | boolean | void {
251
251
  const index = this.sharedModel.getMessageIndex(id);
252
252
  let message = this.sharedModel.getMessage(index);
@@ -398,8 +398,8 @@ export class LabChatModel
398
398
  }
399
399
 
400
400
  private _onchange = async (_: YChat, changes: IChatChanges) => {
401
- if (changes.messageChanges) {
402
- const msgDelta = changes.messageChanges;
401
+ if (changes.messageListChanges) {
402
+ const msgDelta = changes.messageListChanges;
403
403
  let index = 0;
404
404
  for (const delta of msgDelta) {
405
405
  if (delta.retain) {
@@ -411,10 +411,10 @@ export class LabChatModel
411
411
  attachments: attachmentIds,
412
412
  mentions: mentionsIds,
413
413
  ...baseMessage
414
- } = ymessage;
414
+ } = ymessage.toJSON() as IYmessage;
415
415
 
416
416
  // Build the base message with sender.
417
- const msg: IChatMessage = {
417
+ const msg: IMessageContent = {
418
418
  ...baseMessage,
419
419
  sender: this.sharedModel.getUser(sender) || {
420
420
  username: 'User undefined',
@@ -457,6 +457,54 @@ export class LabChatModel
457
457
  }
458
458
  }
459
459
 
460
+ if (changes.messageChanges) {
461
+ // Update change in the message.
462
+ changes.messageChanges.forEach(change => {
463
+ const message = this.messages[change.index];
464
+ if (change.type === 'remove') {
465
+ delete message[change.key as keyof IMessageContent];
466
+ } else if (change.newValue !== undefined) {
467
+ const key = change.key;
468
+ const value = change.newValue;
469
+ if (key === 'attachments') {
470
+ const attachments: IAttachment[] = [];
471
+ (value as string[]).forEach(attachmentId => {
472
+ const attachment = this.sharedModel.getAttachment(attachmentId);
473
+ if (attachment) {
474
+ attachments.push(attachment);
475
+ }
476
+ });
477
+ if (attachments.length) {
478
+ message.update({ attachments });
479
+ } else {
480
+ message.update({ attachments: undefined });
481
+ }
482
+ } else if (key === 'mentions') {
483
+ const mentions: IUser[] = (value as string[]).map(
484
+ user =>
485
+ this.sharedModel.getUser(user) || {
486
+ username: 'User undefined',
487
+ mention_name: 'User-undefined'
488
+ }
489
+ );
490
+ if (mentions?.length) {
491
+ message.update({ mentions });
492
+ }
493
+ } else if (
494
+ ['body', 'time', 'raw_time', 'deleted', 'edited'].includes(key)
495
+ ) {
496
+ const update: Partial<IMessageContent> = {};
497
+ update[key as keyof IMessageContent] = value;
498
+ message.update(update);
499
+ } else {
500
+ console.error(
501
+ `The attribute '${key}' of message cannot be updated`
502
+ );
503
+ }
504
+ }
505
+ });
506
+ }
507
+
460
508
  if (changes.metadataChanges) {
461
509
  changes.metadataChanges.forEach(change => {
462
510
  // no need to search for update or add, if the new value contains ID, let's
package/src/ychat.ts CHANGED
@@ -3,7 +3,7 @@
3
3
  * Distributed under the terms of the Modified BSD License.
4
4
  */
5
5
 
6
- import { IAttachment, IChatMessage, IUser } from '@jupyter/chat';
6
+ import { IAttachment, IMessageContent, IUser } from '@jupyter/chat';
7
7
  import { Delta, DocumentChange, IMapChange, YDocument } from '@jupyter/ydoc';
8
8
  import { JSONExt, JSONObject, PartialJSONValue, UUID } from '@lumino/coreutils';
9
9
  import * as Y from 'yjs';
@@ -11,7 +11,7 @@ import * as Y from 'yjs';
11
11
  /**
12
12
  * The type for a YMessage.
13
13
  */
14
- export type IYmessage = IChatMessage<string, string>;
14
+ export type IYmessage = IMessageContent<string, string>;
15
15
 
16
16
  /**
17
17
  * The type for a YMessage.
@@ -25,7 +25,11 @@ export interface IChatChanges extends DocumentChange {
25
25
  /**
26
26
  * Changes in messages.
27
27
  */
28
- messageChanges?: MessageChange;
28
+ messageListChanges?: MessageListChange;
29
+ /**
30
+ * Changes of one message.
31
+ */
32
+ messageChanges?: MessageChange[];
29
33
  /**
30
34
  * Changes in users.
31
35
  */
@@ -40,10 +44,15 @@ export interface IChatChanges extends DocumentChange {
40
44
  metadataChanges?: MetadataChange[];
41
45
  }
42
46
 
47
+ /**
48
+ * The message list change type.
49
+ */
50
+ export type MessageListChange = Delta<Y.Map<any>[]>;
51
+
43
52
  /**
44
53
  * The message change type.
45
54
  */
46
- export type MessageChange = Delta<IYmessage[]>;
55
+ export type MessageChange = IMapChange<any> & { index: number };
47
56
 
48
57
  /**
49
58
  * The user change type.
@@ -72,8 +81,8 @@ export class YChat extends YDocument<IChatChanges> {
72
81
  this._users = this.ydoc.getMap<IUser>('users');
73
82
  this._users.observe(this._usersObserver);
74
83
 
75
- this._messages = this.ydoc.getArray<IYmessage>('messages');
76
- this._messages.observe(this._messagesObserver);
84
+ this._messages = this.ydoc.getArray<Y.Map<any>>('messages');
85
+ this._messages.observeDeep(this._messagesObserver);
77
86
 
78
87
  this._attachments = this.ydoc.getMap<IAttachment>('attachments');
79
88
  this._attachments.observe(this._attachmentsObserver);
@@ -132,9 +141,15 @@ export class YChat extends YDocument<IChatChanges> {
132
141
 
133
142
  this.transact(() => {
134
143
  const messages = (value['messages'] as unknown as Array<IYmessage>) ?? [];
144
+ const ymessages: Y.Map<any>[] = [];
135
145
  messages.forEach(message => {
136
- this._messages.push([message]);
146
+ const ymessage = new Y.Map<any>();
147
+ for (const [key, value] of Object.entries(message)) {
148
+ ymessage.set(key, value);
149
+ }
150
+ ymessages.push(ymessage);
137
151
  });
152
+ this._messages.push(ymessages);
138
153
 
139
154
  const users = value['users'] ?? {};
140
155
  Object.entries(users).forEach(([key, val]) =>
@@ -167,24 +182,32 @@ export class YChat extends YDocument<IChatChanges> {
167
182
  }
168
183
 
169
184
  getMessage(index: number): IYmessage | undefined {
170
- return this._messages.get(index);
185
+ return this._messages.get(index).toJSON() as IYmessage;
171
186
  }
172
187
 
173
- addMessage(value: IYmessage): void {
188
+ addMessage(msg: IYmessage): void {
174
189
  this.transact(() => {
175
- this._messages.push([value]);
190
+ const ymessage = new Y.Map<any>();
191
+ for (const [key, value] of Object.entries(msg)) {
192
+ ymessage.set(key, value);
193
+ }
194
+ this._messages.push([ymessage]);
176
195
  });
177
196
  }
178
197
 
179
- updateMessage(index: number, value: IYmessage): void {
198
+ updateMessage(index: number, msg: IYmessage): void {
180
199
  this.transact(() => {
181
- this._messages.delete(index);
182
- this._messages.insert(index, [value]);
200
+ const original = this._messages.get(index);
201
+ for (const [key, value] of Object.entries(msg)) {
202
+ if (original.get(key) !== value) {
203
+ original.set(key, value);
204
+ }
205
+ }
183
206
  });
184
207
  }
185
208
 
186
209
  getMessageIndex(id: string): number {
187
- return this._messages.toArray().findIndex(msg => msg.id === id);
210
+ return this._messages.toArray().findIndex(msg => msg.get('id') === id);
188
211
  }
189
212
 
190
213
  deleteMessage(index: number): void {
@@ -258,14 +281,60 @@ export class YChat extends YDocument<IChatChanges> {
258
281
  }
259
282
  });
260
283
 
261
- this._changed.emit({ userChanges } as Partial<IChatChanges>);
284
+ this._changed.emit({ userChanges });
262
285
  };
263
286
 
264
- private _messagesObserver = (event: Y.YArrayEvent<IYmessage>): void => {
265
- const messageChanges = event.delta;
266
- this._changed.emit({
267
- messageChanges: messageChanges
268
- } as Partial<IChatChanges>);
287
+ private _messagesObserver = (
288
+ events: (Y.YArrayEvent<Y.Map<any>> | Y.YMapEvent<any>)[]
289
+ ): void => {
290
+ events.forEach(event => {
291
+ if (event instanceof Y.YArrayEvent) {
292
+ // Change on the message list.
293
+ const messageListChanges = event.delta;
294
+ this._changed.emit({
295
+ messageListChanges
296
+ } as Partial<IChatChanges>);
297
+ } else if (event instanceof Y.YMapEvent) {
298
+ // Change on existing message(s), let's update the whole message.
299
+ const messageChanges = new Array<MessageChange>();
300
+ event.keysChanged.forEach(key => {
301
+ const change = event.changes.keys.get(key);
302
+ if (change) {
303
+ const index = this.getMessageIndex(event.target.get('id'));
304
+ switch (change.action) {
305
+ case 'add':
306
+ messageChanges.push({
307
+ index,
308
+ key,
309
+ newValue: event.target.get(key),
310
+ type: 'add'
311
+ });
312
+ break;
313
+ case 'delete':
314
+ messageChanges.push({
315
+ index,
316
+ key,
317
+ oldValue: change.oldValue,
318
+ type: 'remove'
319
+ });
320
+ break;
321
+ case 'update':
322
+ messageChanges.push({
323
+ index,
324
+ key: key,
325
+ oldValue: change.oldValue,
326
+ newValue: event.target.get(key),
327
+ type: 'change'
328
+ });
329
+ break;
330
+ }
331
+ }
332
+ });
333
+ this._changed.emit({
334
+ messageChanges
335
+ });
336
+ }
337
+ });
269
338
  };
270
339
 
271
340
  private _attachmentsObserver = (event: Y.YMapEvent<IAttachment>): void => {
@@ -300,7 +369,7 @@ export class YChat extends YDocument<IChatChanges> {
300
369
  }
301
370
  });
302
371
 
303
- this._changed.emit({ attachmentChanges } as Partial<IChatChanges>);
372
+ this._changed.emit({ attachmentChanges });
304
373
  };
305
374
 
306
375
  private _metadataObserver = (event: Y.YMapEvent<IMetadata>): void => {
@@ -332,11 +401,11 @@ export class YChat extends YDocument<IChatChanges> {
332
401
  }
333
402
  });
334
403
 
335
- this._changed.emit({ metadataChanges } as Partial<IChatChanges>);
404
+ this._changed.emit({ metadataChanges });
336
405
  };
337
406
 
338
407
  private _users: Y.Map<IUser>;
339
- private _messages: Y.Array<IYmessage>;
408
+ private _messages: Y.Array<Y.Map<any>>;
340
409
  private _attachments: Y.Map<IAttachment>;
341
410
  private _metadata: Y.Map<IMetadata>;
342
411
  }