@hocuspocus/provider 2.13.7 → 2.14.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.
@@ -88,6 +88,8 @@ export declare class HocuspocusProvider extends EventEmitter {
88
88
  intervals: any;
89
89
  isConnected: boolean;
90
90
  constructor(configuration: HocuspocusProviderConfiguration);
91
+ boundDocumentUpdateHandler: (update: Uint8Array, origin: any) => void;
92
+ boundAwarenessUpdateHandler: ({ added, updated, removed }: any, origin: any) => void;
91
93
  boundBroadcastChannelSubscriber: (data: ArrayBuffer) => void;
92
94
  boundPageHide: () => void;
93
95
  boundOnOpen: (event: Event) => Promise<void>;
@@ -2,8 +2,13 @@ import type { AbstractType, YArrayEvent } from 'yjs';
2
2
  import * as Y from 'yjs';
3
3
  import { HocuspocusProvider, HocuspocusProviderConfiguration } from './HocuspocusProvider.js';
4
4
  import { TiptapCollabProviderWebsocket } from './TiptapCollabProviderWebsocket.js';
5
- import type { TCollabComment, TCollabThread, THistoryVersion } from './types.js';
6
- export type TiptapCollabProviderConfiguration = Required<Pick<HocuspocusProviderConfiguration, 'name'>> & Partial<HocuspocusProviderConfiguration> & (Required<Pick<AdditionalTiptapCollabProviderConfiguration, 'websocketProvider'>> | Required<Pick<AdditionalTiptapCollabProviderConfiguration, 'appId'>> | Required<Pick<AdditionalTiptapCollabProviderConfiguration, 'baseUrl'>>) & Pick<AdditionalTiptapCollabProviderConfiguration, 'user'>;
5
+ import type { DeleteCommentOptions, TCollabComment, TCollabThread, THistoryVersion } from './types.js';
6
+ export type TiptapCollabProviderConfiguration = Required<Pick<HocuspocusProviderConfiguration, 'name'>> & Partial<HocuspocusProviderConfiguration> & (Required<Pick<AdditionalTiptapCollabProviderConfiguration, 'websocketProvider'>> | Required<Pick<AdditionalTiptapCollabProviderConfiguration, 'appId'>> | Required<Pick<AdditionalTiptapCollabProviderConfiguration, 'baseUrl'>>) & Pick<AdditionalTiptapCollabProviderConfiguration, 'user'> & {
7
+ /**
8
+ * Pass `true` if you want to delete a thread when the first comment is deleted.
9
+ */
10
+ deleteThreadOnFirstCommentDelete?: boolean;
11
+ };
7
12
  export interface AdditionalTiptapCollabProviderConfiguration {
8
13
  /**
9
14
  * A Hocuspocus Cloud App ID, get one here: https://cloud.tiptap.dev
@@ -43,21 +48,103 @@ export declare class TiptapCollabProvider extends HocuspocusProvider {
43
48
  isAutoVersioning(): boolean;
44
49
  enableAutoVersioning(): 1;
45
50
  disableAutoVersioning(): 0;
51
+ /**
52
+ * Returns all users in the document as Y.Map objects
53
+ * @returns An array of Y.Map objects
54
+ */
46
55
  private getYThreads;
56
+ /**
57
+ * Finds all threads in the document and returns them as JSON objects
58
+ * @returns An array of threads as JSON objects
59
+ */
47
60
  getThreads<Data, CommentData>(): TCollabThread<Data, CommentData>[];
61
+ /**
62
+ * Find the index of a thread by its id
63
+ * @param id The thread id
64
+ * @returns The index of the thread or null if not found
65
+ */
48
66
  private getThreadIndex;
67
+ /**
68
+ * Gets a single thread by its id
69
+ * @param id The thread id
70
+ * @returns The thread as a JSON object or null if not found
71
+ */
49
72
  getThread<Data, CommentData>(id: string): TCollabThread<Data, CommentData> | null;
73
+ /**
74
+ * Gets a single thread by its id as a Y.Map object
75
+ * @param id The thread id
76
+ * @returns The thread as a Y.Map object or null if not found
77
+ */
50
78
  private getYThread;
51
- createThread(data: Omit<TCollabThread, 'id' | 'createdAt' | 'updatedAt' | 'comments'>): TCollabThread;
79
+ /**
80
+ * Create a new thread
81
+ * @param data The thread data
82
+ * @returns The created thread
83
+ */
84
+ createThread(data: Omit<TCollabThread, 'id' | 'createdAt' | 'updatedAt' | 'comments' | 'deletedComments'>): TCollabThread;
85
+ /**
86
+ * Update a specific thread
87
+ * @param id The thread id
88
+ * @param data New data for the thread
89
+ * @returns The updated thread or null if the thread is not found
90
+ */
52
91
  updateThread(id: TCollabThread['id'], data: Partial<Pick<TCollabThread, 'data'> & {
53
92
  resolvedAt: TCollabThread['resolvedAt'] | null;
54
93
  }>): TCollabThread;
94
+ /**
95
+ * Delete a specific thread and all its comments
96
+ * @param id The thread id
97
+ * @returns void
98
+ */
55
99
  deleteThread(id: TCollabThread['id']): void;
56
- getThreadComments(threadId: TCollabThread['id']): TCollabComment[] | null;
57
- getThreadComment(threadId: TCollabThread['id'], commentId: TCollabComment['id']): TCollabComment | null;
100
+ /**
101
+ * Returns comments from a thread, either deleted or not
102
+ * @param threadId The thread id
103
+ * @param includeDeleted If you want to include deleted comments, defaults to `false`
104
+ * @returns The comments or null if the thread is not found
105
+ */
106
+ getThreadComments(threadId: TCollabThread['id'], includeDeleted?: boolean): TCollabComment[] | null;
107
+ /**
108
+ * Get a single comment from a specific thread
109
+ * @param threadId The thread id
110
+ * @param commentId The comment id
111
+ * @param includeDeleted If you want to include deleted comments in the search
112
+ * @returns The comment or null if not found
113
+ */
114
+ getThreadComment(threadId: TCollabThread['id'], commentId: TCollabComment['id'], includeDeleted?: boolean): TCollabComment | null;
115
+ /**
116
+ * Adds a comment to a thread
117
+ * @param threadId The thread id
118
+ * @param data The comment data
119
+ * @returns The updated thread or null if the thread is not found
120
+ * @example addComment('123', { content: 'Hello world', data: { author: 'Maria Doe' } })
121
+ */
58
122
  addComment(threadId: TCollabThread['id'], data: Omit<TCollabComment, 'id' | 'updatedAt' | 'createdAt'>): TCollabThread;
123
+ /**
124
+ * Update a comment in a thread
125
+ * @param threadId The thread id
126
+ * @param commentId The comment id
127
+ * @param data The new comment data
128
+ * @returns The updated thread or null if the thread or comment is not found
129
+ * @example updateComment('123', { content: 'The new content', data: { attachments: ['file1.jpg'] }})
130
+ */
59
131
  updateComment(threadId: TCollabThread['id'], commentId: TCollabComment['id'], data: Partial<Pick<TCollabComment, 'data' | 'content'>>): TCollabThread;
60
- deleteComment(threadId: TCollabThread['id'], commentId: TCollabComment['id']): TCollabThread | null | undefined;
132
+ /**
133
+ * Deletes a comment from a thread
134
+ * @param threadId The thread id
135
+ * @param commentId The comment id
136
+ * @param options A set of options that control how the comment is deleted
137
+ * @returns The updated thread or null if the thread or comment is not found
138
+ */
139
+ deleteComment(threadId: TCollabThread['id'], commentId: TCollabComment['id'], options: DeleteCommentOptions): TCollabThread | null | undefined;
140
+ /**
141
+ * Start watching threads for changes
142
+ * @param callback The callback function to be called when a thread changes
143
+ */
61
144
  watchThreads(callback: () => void): void;
145
+ /**
146
+ * Stop watching threads for changes
147
+ * @param callback The callback function to be removed
148
+ */
62
149
  unwatchThreads(callback: () => void): void;
63
150
  }
@@ -90,12 +90,14 @@ export type TCollabThread<Data = any, CommentData = any> = {
90
90
  updatedAt: number;
91
91
  resolvedAt?: string;
92
92
  comments: TCollabComment<CommentData>[];
93
+ deletedComments: TCollabComment<CommentData>[];
93
94
  data: Data;
94
95
  };
95
96
  export type TCollabComment<Data = any> = {
96
97
  id: string;
97
- createdAt: number;
98
- updatedAt: number;
98
+ createdAt: string;
99
+ updatedAt: string;
100
+ deletedAt?: string;
99
101
  data: Data;
100
102
  content: any;
101
103
  };
@@ -144,3 +146,13 @@ export type THistoryDocumentRevertedEvent = {
144
146
  event: 'document.reverted';
145
147
  version: number;
146
148
  };
149
+ export type DeleteCommentOptions = {
150
+ /**
151
+ * If `true`, the thread will also be deleted if the deleted comment was the first comment in the thread.
152
+ */
153
+ deleteThread?: boolean;
154
+ /**
155
+ * If `true`, will remove the content of the deleted comment
156
+ */
157
+ deleteContent?: boolean;
158
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hocuspocus/provider",
3
- "version": "2.13.7",
3
+ "version": "2.14.0",
4
4
  "description": "hocuspocus provider",
5
5
  "homepage": "https://hocuspocus.dev",
6
6
  "keywords": [
@@ -29,7 +29,7 @@
29
29
  "dist"
30
30
  ],
31
31
  "dependencies": {
32
- "@hocuspocus/common": "^2.13.7",
32
+ "@hocuspocus/common": "^2.14.0",
33
33
  "@lifeomic/attempt": "^3.0.2",
34
34
  "lib0": "^0.2.87",
35
35
  "ws": "^8.17.1"
@@ -212,8 +212,8 @@ export class HocuspocusProvider extends EventEmitter {
212
212
  this.emit('awarenessChange', { states: awarenessStatesToArray(this.awareness!.getStates()) })
213
213
  })
214
214
 
215
- this.document.on('update', this.documentUpdateHandler.bind(this))
216
- this.awareness?.on('update', this.awarenessUpdateHandler.bind(this))
215
+ this.document.on('update', this.boundDocumentUpdateHandler)
216
+ this.awareness?.on('update', this.boundAwarenessUpdateHandler)
217
217
  this.registerEventListeners()
218
218
 
219
219
  if (
@@ -229,6 +229,10 @@ export class HocuspocusProvider extends EventEmitter {
229
229
  this.configuration.websocketProvider.attach(this)
230
230
  }
231
231
 
232
+ boundDocumentUpdateHandler = this.documentUpdateHandler.bind(this)
233
+
234
+ boundAwarenessUpdateHandler = this.awarenessUpdateHandler.bind(this)
235
+
232
236
  boundBroadcastChannelSubscriber = this.broadcastChannelSubscriber.bind(this)
233
237
 
234
238
  boundPageHide = this.pageHide.bind(this)
@@ -490,11 +494,11 @@ export class HocuspocusProvider extends EventEmitter {
490
494
 
491
495
  if (this.awareness) {
492
496
  removeAwarenessStates(this.awareness, [this.document.clientID], 'provider destroy')
493
- this.awareness.off('update', this.awarenessUpdateHandler)
497
+ this.awareness.off('update', this.boundAwarenessUpdateHandler)
494
498
  this.awareness.destroy()
495
499
  }
496
500
 
497
- this.document.off('update', this.documentUpdateHandler)
501
+ this.document.off('update', this.boundDocumentUpdateHandler)
498
502
 
499
503
  this.removeAllListeners()
500
504
 
@@ -8,16 +8,27 @@ import {
8
8
 
9
9
  import { TiptapCollabProviderWebsocket } from './TiptapCollabProviderWebsocket.js'
10
10
  import type {
11
+ DeleteCommentOptions,
11
12
  TCollabComment, TCollabThread, THistoryVersion,
12
13
  } from './types.js'
13
14
 
15
+ const defaultDeleteCommentOptions: DeleteCommentOptions = {
16
+ deleteContent: false,
17
+ deleteThread: false,
18
+ }
19
+
14
20
  export type TiptapCollabProviderConfiguration =
15
21
  Required<Pick<HocuspocusProviderConfiguration, 'name'>> &
16
22
  Partial<HocuspocusProviderConfiguration> &
17
23
  (Required<Pick<AdditionalTiptapCollabProviderConfiguration, 'websocketProvider'>> |
18
24
  Required<Pick<AdditionalTiptapCollabProviderConfiguration, 'appId'>>|
19
25
  Required<Pick<AdditionalTiptapCollabProviderConfiguration, 'baseUrl'>>) &
20
- Pick<AdditionalTiptapCollabProviderConfiguration, 'user'>
26
+ Pick<AdditionalTiptapCollabProviderConfiguration, 'user'> & {
27
+ /**
28
+ * Pass `true` if you want to delete a thread when the first comment is deleted.
29
+ */
30
+ deleteThreadOnFirstCommentDelete?: boolean,
31
+ }
21
32
 
22
33
  export interface AdditionalTiptapCollabProviderConfiguration {
23
34
  /**
@@ -107,14 +118,27 @@ export class TiptapCollabProvider extends HocuspocusProvider {
107
118
  return this.configuration.document.getMap<number>(`${this.tiptapCollabConfigurationPrefix}config`).set('autoVersioning', 0)
108
119
  }
109
120
 
121
+ /**
122
+ * Returns all users in the document as Y.Map objects
123
+ * @returns An array of Y.Map objects
124
+ */
110
125
  private getYThreads() {
111
126
  return this.configuration.document.getArray<Y.Map<any>>(`${this.tiptapCollabConfigurationPrefix}threads`)
112
127
  }
113
128
 
129
+ /**
130
+ * Finds all threads in the document and returns them as JSON objects
131
+ * @returns An array of threads as JSON objects
132
+ */
114
133
  getThreads<Data, CommentData>(): TCollabThread<Data, CommentData>[] {
115
134
  return this.getYThreads().toJSON() as TCollabThread<Data, CommentData>[]
116
135
  }
117
136
 
137
+ /**
138
+ * Find the index of a thread by its id
139
+ * @param id The thread id
140
+ * @returns The index of the thread or null if not found
141
+ */
118
142
  private getThreadIndex(id: string): number | null {
119
143
  let index = null
120
144
 
@@ -131,6 +155,11 @@ export class TiptapCollabProvider extends HocuspocusProvider {
131
155
  return index
132
156
  }
133
157
 
158
+ /**
159
+ * Gets a single thread by its id
160
+ * @param id The thread id
161
+ * @returns The thread as a JSON object or null if not found
162
+ */
134
163
  getThread<Data, CommentData>(id: string): TCollabThread<Data, CommentData> | null {
135
164
  const index = this.getThreadIndex(id)
136
165
 
@@ -141,6 +170,11 @@ export class TiptapCollabProvider extends HocuspocusProvider {
141
170
  return this.getYThreads().get(index).toJSON() as TCollabThread<Data, CommentData>
142
171
  }
143
172
 
173
+ /**
174
+ * Gets a single thread by its id as a Y.Map object
175
+ * @param id The thread id
176
+ * @returns The thread as a Y.Map object or null if not found
177
+ */
144
178
  private getYThread(id: string) {
145
179
  const index = this.getThreadIndex(id)
146
180
 
@@ -151,7 +185,12 @@ export class TiptapCollabProvider extends HocuspocusProvider {
151
185
  return this.getYThreads().get(index)
152
186
  }
153
187
 
154
- createThread(data: Omit<TCollabThread, 'id' | 'createdAt' | 'updatedAt' | 'comments'>) {
188
+ /**
189
+ * Create a new thread
190
+ * @param data The thread data
191
+ * @returns The created thread
192
+ */
193
+ createThread(data: Omit<TCollabThread, 'id' | 'createdAt' | 'updatedAt' | 'comments' | 'deletedComments'>) {
155
194
  let createdThread: TCollabThread = {} as TCollabThread
156
195
 
157
196
  this.document.transact(() => {
@@ -159,6 +198,7 @@ export class TiptapCollabProvider extends HocuspocusProvider {
159
198
  thread.set('id', uuidv4())
160
199
  thread.set('createdAt', (new Date()).toISOString())
161
200
  thread.set('comments', new Y.Array())
201
+ thread.set('deletedComments', new Y.Array())
162
202
 
163
203
  this.getYThreads().push([thread])
164
204
  createdThread = this.updateThread(String(thread.get('id')), data)
@@ -167,6 +207,12 @@ export class TiptapCollabProvider extends HocuspocusProvider {
167
207
  return createdThread
168
208
  }
169
209
 
210
+ /**
211
+ * Update a specific thread
212
+ * @param id The thread id
213
+ * @param data New data for the thread
214
+ * @returns The updated thread or null if the thread is not found
215
+ */
170
216
  updateThread(id: TCollabThread['id'], data: Partial<Pick<TCollabThread, 'data'> & {
171
217
  resolvedAt: TCollabThread['resolvedAt'] | null
172
218
  }>) {
@@ -195,6 +241,11 @@ export class TiptapCollabProvider extends HocuspocusProvider {
195
241
  return updatedThread
196
242
  }
197
243
 
244
+ /**
245
+ * Delete a specific thread and all its comments
246
+ * @param id The thread id
247
+ * @returns void
248
+ */
198
249
  deleteThread(id: TCollabThread['id']) {
199
250
  const index = this.getThreadIndex(id)
200
251
 
@@ -205,26 +256,52 @@ export class TiptapCollabProvider extends HocuspocusProvider {
205
256
  this.getYThreads().delete(index, 1)
206
257
  }
207
258
 
208
- getThreadComments(threadId: TCollabThread['id']): TCollabComment[] | null {
259
+ /**
260
+ * Returns comments from a thread, either deleted or not
261
+ * @param threadId The thread id
262
+ * @param includeDeleted If you want to include deleted comments, defaults to `false`
263
+ * @returns The comments or null if the thread is not found
264
+ */
265
+ getThreadComments(threadId: TCollabThread['id'], includeDeleted?: boolean): TCollabComment[] | null {
209
266
  const index = this.getThreadIndex(threadId)
210
267
 
211
268
  if (index === null) {
212
269
  return null
213
270
  }
214
271
 
215
- return this.getThread(threadId)?.comments ?? []
272
+ const comments = !includeDeleted ? this.getThread(threadId)?.comments : [...(this.getThread(threadId)?.comments || []), ...(this.getThread(threadId)?.deletedComments || [])].sort((a, b) => {
273
+ return a.createdAt.localeCompare(b.createdAt)
274
+ })
275
+
276
+ return comments ?? []
216
277
  }
217
278
 
218
- getThreadComment(threadId: TCollabThread['id'], commentId: TCollabComment['id']): TCollabComment | null {
279
+ /**
280
+ * Get a single comment from a specific thread
281
+ * @param threadId The thread id
282
+ * @param commentId The comment id
283
+ * @param includeDeleted If you want to include deleted comments in the search
284
+ * @returns The comment or null if not found
285
+ */
286
+ getThreadComment(threadId: TCollabThread['id'], commentId: TCollabComment['id'], includeDeleted?: boolean): TCollabComment | null {
219
287
  const index = this.getThreadIndex(threadId)
220
288
 
221
289
  if (index === null) {
222
290
  return null
223
291
  }
224
292
 
225
- return this.getThread(threadId)?.comments.find(comment => comment.id === commentId) ?? null
293
+ const comments = this.getThreadComments(threadId, includeDeleted)
294
+
295
+ return comments?.find(comment => comment.id === commentId) ?? null
226
296
  }
227
297
 
298
+ /**
299
+ * Adds a comment to a thread
300
+ * @param threadId The thread id
301
+ * @param data The comment data
302
+ * @returns The updated thread or null if the thread is not found
303
+ * @example addComment('123', { content: 'Hello world', data: { author: 'Maria Doe' } })
304
+ */
228
305
  addComment(threadId: TCollabThread['id'], data: Omit<TCollabComment, 'id' | 'updatedAt' | 'createdAt'>) {
229
306
  let updatedThread: TCollabThread = {} as TCollabThread
230
307
 
@@ -246,6 +323,14 @@ export class TiptapCollabProvider extends HocuspocusProvider {
246
323
  return updatedThread
247
324
  }
248
325
 
326
+ /**
327
+ * Update a comment in a thread
328
+ * @param threadId The thread id
329
+ * @param commentId The comment id
330
+ * @param data The new comment data
331
+ * @returns The updated thread or null if the thread or comment is not found
332
+ * @example updateComment('123', { content: 'The new content', data: { attachments: ['file1.jpg'] }})
333
+ */
249
334
  updateComment(threadId: TCollabThread['id'], commentId: TCollabComment['id'], data: Partial<Pick<TCollabComment, 'data' | 'content'>>) {
250
335
  let updatedThread: TCollabThread = {} as TCollabThread
251
336
 
@@ -281,7 +366,16 @@ export class TiptapCollabProvider extends HocuspocusProvider {
281
366
  return updatedThread
282
367
  }
283
368
 
284
- deleteComment(threadId: TCollabThread['id'], commentId: TCollabComment['id']) {
369
+ /**
370
+ * Deletes a comment from a thread
371
+ * @param threadId The thread id
372
+ * @param commentId The comment id
373
+ * @param options A set of options that control how the comment is deleted
374
+ * @returns The updated thread or null if the thread or comment is not found
375
+ */
376
+ deleteComment(threadId: TCollabThread['id'], commentId: TCollabComment['id'], options: DeleteCommentOptions) {
377
+ const { deleteContent, deleteThread } = { ...defaultDeleteCommentOptions, ...options }
378
+
285
379
  const thread = this.getYThread(threadId)
286
380
 
287
381
  if (thread === null) return null
@@ -297,22 +391,39 @@ export class TiptapCollabProvider extends HocuspocusProvider {
297
391
 
298
392
  // if the first comment of a thread is deleted we also
299
393
  // delete the thread itself as the source comment is gone
300
- if (commentIndex === 0) {
394
+ if (commentIndex === 0 && (deleteThread || (this.configuration as TiptapCollabProviderConfiguration).deleteThreadOnFirstCommentDelete)) {
301
395
  this.deleteThread(threadId)
302
396
  return
303
397
  }
304
398
 
305
- if (commentIndex > 0) {
306
- thread.get('comments').delete(commentIndex)
307
- }
399
+ const comment = thread.get('comments').get(commentIndex)
400
+ const newComment = new Y.Map()
401
+
402
+ newComment.set('id', comment.get('id'))
403
+ newComment.set('createdAt', comment.get('createdAt'))
404
+ newComment.set('updatedAt', (new Date()).toISOString())
405
+ newComment.set('deletedAt', (new Date()).toISOString())
406
+ newComment.set('data', comment.get('data'))
407
+ newComment.set('content', deleteContent ? null : comment.get('content'))
408
+
409
+ thread.get('deletedComments').push([newComment])
410
+ thread.get('comments').delete(commentIndex)
308
411
 
309
412
  return thread.toJSON() as TCollabThread
310
413
  }
311
414
 
415
+ /**
416
+ * Start watching threads for changes
417
+ * @param callback The callback function to be called when a thread changes
418
+ */
312
419
  watchThreads(callback: () => void) {
313
420
  this.getYThreads().observeDeep(callback)
314
421
  }
315
422
 
423
+ /**
424
+ * Stop watching threads for changes
425
+ * @param callback The callback function to be removed
426
+ */
316
427
  unwatchThreads(callback: () => void) {
317
428
  this.getYThreads().unobserveDeep(callback)
318
429
  }
package/src/types.ts CHANGED
@@ -112,13 +112,15 @@ export type TCollabThread<Data = any, CommentData = any> = {
112
112
  updatedAt: number;
113
113
  resolvedAt?: string; // (new Date()).toISOString()
114
114
  comments: TCollabComment<CommentData>[];
115
+ deletedComments: TCollabComment<CommentData>[];
115
116
  data: Data
116
117
  }
117
118
 
118
119
  export type TCollabComment<Data = any> = {
119
120
  id: string;
120
- createdAt: number;
121
- updatedAt: number;
121
+ createdAt: string;
122
+ updatedAt: string;
123
+ deletedAt?: string;
122
124
  data: Data
123
125
  content: any
124
126
  }
@@ -183,3 +185,15 @@ export type THistoryDocumentRevertedEvent = {
183
185
  event: 'document.reverted';
184
186
  version: number;
185
187
  };
188
+
189
+ export type DeleteCommentOptions = {
190
+ /**
191
+ * If `true`, the thread will also be deleted if the deleted comment was the first comment in the thread.
192
+ */
193
+ deleteThread?: boolean
194
+
195
+ /**
196
+ * If `true`, will remove the content of the deleted comment
197
+ */
198
+ deleteContent?: boolean
199
+ }