@hocuspocus/provider 2.13.6 → 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.
- package/dist/hocuspocus-provider.cjs +116 -15
- package/dist/hocuspocus-provider.cjs.map +1 -1
- package/dist/hocuspocus-provider.esm.js +116 -15
- package/dist/hocuspocus-provider.esm.js.map +1 -1
- package/dist/packages/extension-redis/src/Redis.d.ts +6 -0
- package/dist/packages/provider/src/HocuspocusProvider.d.ts +2 -0
- package/dist/packages/provider/src/TiptapCollabProvider.d.ts +93 -6
- package/dist/packages/provider/src/types.d.ts +14 -2
- package/package.json +2 -2
- package/src/HocuspocusProvider.ts +8 -4
- package/src/TiptapCollabProvider.ts +122 -11
- package/src/types.ts +16 -2
|
@@ -65,6 +65,12 @@ export declare class Redis implements Extension {
|
|
|
65
65
|
locks: Map<string, Redlock.Lock>;
|
|
66
66
|
logger: Debugger;
|
|
67
67
|
messagePrefix: Buffer;
|
|
68
|
+
/**
|
|
69
|
+
* When we have a high frequency of updates to a document we don't need tons of setTimeouts
|
|
70
|
+
* piling up, so we'll track them to keep it to the most recent per document.
|
|
71
|
+
*/
|
|
72
|
+
private pendingDisconnects;
|
|
73
|
+
private pendingAfterStoreDocumentResolves;
|
|
68
74
|
constructor(configuration: Partial<Configuration>);
|
|
69
75
|
onConfigure({ instance }: onConfigurePayload): Promise<void>;
|
|
70
76
|
private getKey;
|
|
@@ -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
|
-
|
|
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
|
-
|
|
57
|
-
|
|
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
|
-
|
|
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:
|
|
98
|
-
updatedAt:
|
|
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.
|
|
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.
|
|
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.
|
|
216
|
-
this.awareness?.on('update', 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.
|
|
497
|
+
this.awareness.off('update', this.boundAwarenessUpdateHandler)
|
|
494
498
|
this.awareness.destroy()
|
|
495
499
|
}
|
|
496
500
|
|
|
497
|
-
this.document.off('update', this.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
306
|
-
|
|
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:
|
|
121
|
-
updatedAt:
|
|
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
|
+
}
|