@hocuspocus/provider 2.9.0 → 2.9.2-rc.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.
@@ -1,13 +1,17 @@
1
1
  import type { AbstractType, YArrayEvent } from 'yjs';
2
2
  import { HocuspocusProvider, HocuspocusProviderConfiguration } from './HocuspocusProvider.js';
3
3
  import { TiptapCollabProviderWebsocket } from './TiptapCollabProviderWebsocket.js';
4
- import type { THistoryVersion } from './types';
5
- export type TiptapCollabProviderConfiguration = Required<Pick<HocuspocusProviderConfiguration, 'name'>> & Partial<HocuspocusProviderConfiguration> & (Required<Pick<AdditionalTiptapCollabProviderConfiguration, 'websocketProvider'>> | Required<Pick<AdditionalTiptapCollabProviderConfiguration, 'appId'>>);
4
+ import type { TCollabComment, TCollabThread, THistoryVersion } from './types.js';
5
+ export type TiptapCollabProviderConfiguration = Required<Pick<HocuspocusProviderConfiguration, 'name'>> & Partial<HocuspocusProviderConfiguration> & (Required<Pick<AdditionalTiptapCollabProviderConfiguration, 'websocketProvider'>> | Required<Pick<AdditionalTiptapCollabProviderConfiguration, 'appId'>> | Required<Pick<AdditionalTiptapCollabProviderConfiguration, 'baseUrl'>>);
6
6
  export interface AdditionalTiptapCollabProviderConfiguration {
7
7
  /**
8
8
  * A Hocuspocus Cloud App ID, get one here: https://cloud.tiptap.dev
9
9
  */
10
10
  appId?: string;
11
+ /**
12
+ * If you are using the on-premise version of TiptapCollab, put your baseUrl here (e.g. https://collab.yourdomain.com)
13
+ */
14
+ baseUrl?: string;
11
15
  websocketProvider?: TiptapCollabProviderWebsocket;
12
16
  }
13
17
  export declare class TiptapCollabProvider extends HocuspocusProvider {
@@ -36,4 +40,19 @@ export declare class TiptapCollabProvider extends HocuspocusProvider {
36
40
  isAutoVersioning(): boolean;
37
41
  enableAutoVersioning(): 1;
38
42
  disableAutoVersioning(): 0;
43
+ private getYThreads;
44
+ getThreads<Data, CommentData>(): TCollabThread<Data, CommentData>[];
45
+ private getThreadIndex;
46
+ getThread<Data, CommentData>(id: string): TCollabThread<Data, CommentData> | null;
47
+ private getYThread;
48
+ createThread(data: TCollabThread): TCollabThread | null;
49
+ updateThread(id: TCollabThread['id'], data: Pick<TCollabThread, 'data'>): TCollabThread | null;
50
+ deleteThread(id: TCollabThread['id']): void;
51
+ getThreadComments(threadId: TCollabThread['id']): TCollabComment[] | null;
52
+ getThreadComment(threadId: TCollabThread['id'], commentId: TCollabComment['id']): TCollabComment | null;
53
+ addComment(threadId: TCollabThread['id'], data: TCollabComment): TCollabThread | null;
54
+ updateComment(threadId: TCollabThread['id'], commentId: TCollabComment['id'], data: TCollabComment): TCollabThread | null;
55
+ deleteComment(threadId: TCollabThread['id'], commentId: TCollabComment['id']): TCollabThread | null;
56
+ watchThreads(callback: () => void): void;
57
+ unwatchThreads(callback: () => void): void;
39
58
  }
@@ -4,7 +4,11 @@ export interface AdditionalTiptapCollabProviderWebsocketConfiguration {
4
4
  /**
5
5
  * A Hocuspocus Cloud App ID, get one here: https://cloud.tiptap.dev
6
6
  */
7
- appId: string;
7
+ appId?: string;
8
+ /**
9
+ * If you are using the on-premise version of TiptapCollab, put your baseUrl here (e.g. https://collab.yourdomain.com)
10
+ */
11
+ baseUrl?: string;
8
12
  }
9
13
  export declare class TiptapCollabProviderWebsocket extends HocuspocusProviderWebsocket {
10
14
  constructor(configuration: TiptapCollabProviderWebsocketConfiguration);
@@ -84,6 +84,20 @@ export type StatesArray = {
84
84
  clientId: number;
85
85
  [key: string | number]: any;
86
86
  }[];
87
+ export type TCollabThread<Data = any, CommentData = any> = {
88
+ id: string;
89
+ createdAt: number;
90
+ updatedAt: number;
91
+ comments: TCollabComment<CommentData>[];
92
+ data: Data;
93
+ };
94
+ export type TCollabComment<Data = any> = {
95
+ id: string;
96
+ createdAt: number;
97
+ updatedAt: number;
98
+ data: Data;
99
+ content: any;
100
+ };
87
101
  export type THistoryVersion = {
88
102
  name?: string;
89
103
  version: number;
@@ -1,7 +1,7 @@
1
1
  /// <reference types="node" />
2
2
  import { IncomingMessage } from 'http';
3
3
  import WebSocket, { AddressInfo } from 'ws';
4
- import { Server as HocuspocusServer } from './Server';
4
+ import { Server as HocuspocusServer } from './Server.js';
5
5
  import { Debugger } from './Debugger.js';
6
6
  import { DirectConnection } from './DirectConnection.js';
7
7
  import Document from './Document.js';
@@ -1,7 +1,7 @@
1
1
  /// <reference types="node" />
2
2
  import { IncomingMessage, Server as HTTPServer, ServerResponse } from 'http';
3
3
  import { WebSocketServer } from 'ws';
4
- import { Hocuspocus } from './Hocuspocus';
4
+ import { Hocuspocus } from './Hocuspocus.js';
5
5
  export declare class Server {
6
6
  httpServer: HTTPServer;
7
7
  webSocketServer: WebSocketServer;
@@ -1,2 +1,2 @@
1
- declare const _default: import("vite").UserConfigExport;
1
+ declare const _default: import("vite").UserConfig;
2
2
  export default _default;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hocuspocus/provider",
3
- "version": "2.9.0",
3
+ "version": "2.9.2-rc.0",
4
4
  "description": "hocuspocus provider",
5
5
  "homepage": "https://hocuspocus.dev",
6
6
  "keywords": [
@@ -29,9 +29,10 @@
29
29
  "dist"
30
30
  ],
31
31
  "dependencies": {
32
- "@hocuspocus/common": "^2.9.0",
32
+ "@hocuspocus/common": "^2.9.2-rc.0",
33
33
  "@lifeomic/attempt": "^3.0.2",
34
34
  "lib0": "^0.2.87",
35
+ "uuid": "^9.0.0",
35
36
  "ws": "^8.14.2"
36
37
  },
37
38
  "peerDependencies": {
@@ -1,17 +1,22 @@
1
1
  import type { AbstractType, YArrayEvent } from 'yjs'
2
+ import * as Y from 'yjs'
3
+ import { uuidv4 } from 'lib0/random'
2
4
  import {
3
5
  HocuspocusProvider,
4
6
  HocuspocusProviderConfiguration,
5
7
  } from './HocuspocusProvider.js'
6
8
 
7
9
  import { TiptapCollabProviderWebsocket } from './TiptapCollabProviderWebsocket.js'
8
- import type { THistoryVersion } from './types'
10
+ import type {
11
+ TCollabComment, TCollabThread, THistoryVersion,
12
+ } from './types.js'
9
13
 
10
14
  export type TiptapCollabProviderConfiguration =
11
15
  Required<Pick<HocuspocusProviderConfiguration, 'name'>> &
12
16
  Partial<HocuspocusProviderConfiguration> &
13
17
  (Required<Pick<AdditionalTiptapCollabProviderConfiguration, 'websocketProvider'>> |
14
- Required<Pick<AdditionalTiptapCollabProviderConfiguration, 'appId'>>)
18
+ Required<Pick<AdditionalTiptapCollabProviderConfiguration, 'appId'>>|
19
+ Required<Pick<AdditionalTiptapCollabProviderConfiguration, 'baseUrl'>>)
15
20
 
16
21
  export interface AdditionalTiptapCollabProviderConfiguration {
17
22
  /**
@@ -19,6 +24,11 @@ export interface AdditionalTiptapCollabProviderConfiguration {
19
24
  */
20
25
  appId?: string,
21
26
 
27
+ /**
28
+ * If you are using the on-premise version of TiptapCollab, put your baseUrl here (e.g. https://collab.yourdomain.com)
29
+ */
30
+ baseUrl?: string
31
+
22
32
  websocketProvider?: TiptapCollabProviderWebsocket
23
33
  }
24
34
 
@@ -27,7 +37,7 @@ export class TiptapCollabProvider extends HocuspocusProvider {
27
37
 
28
38
  constructor(configuration: TiptapCollabProviderConfiguration) {
29
39
  if (!configuration.websocketProvider) {
30
- configuration.websocketProvider = new TiptapCollabProviderWebsocket({ appId: (configuration as Required<Pick<AdditionalTiptapCollabProviderConfiguration, 'appId'>>).appId })
40
+ configuration.websocketProvider = new TiptapCollabProviderWebsocket({ appId: (configuration as Required<Pick<AdditionalTiptapCollabProviderConfiguration, 'appId'>>).appId, baseUrl: (configuration as Required<Pick<AdditionalTiptapCollabProviderConfiguration, 'baseUrl'>>).baseUrl })
31
41
  }
32
42
 
33
43
  if (!configuration.token) {
@@ -87,4 +97,168 @@ export class TiptapCollabProvider extends HocuspocusProvider {
87
97
  return this.configuration.document.getMap<number>(`${this.tiptapCollabConfigurationPrefix}config`).set('autoVersioning', 0)
88
98
  }
89
99
 
100
+ private getYThreads() {
101
+ return this.configuration.document.getArray<Y.Map<any>>(`${this.tiptapCollabConfigurationPrefix}threads`)
102
+ }
103
+
104
+ getThreads<Data, CommentData>(): TCollabThread<Data, CommentData>[] {
105
+ return this.getYThreads().toJSON() as TCollabThread<Data, CommentData>[]
106
+ }
107
+
108
+ private getThreadIndex(id: string): number | null {
109
+ let index = null
110
+
111
+ let i = 0
112
+ // eslint-disable-next-line no-restricted-syntax
113
+ for (const thread of this.getThreads()) {
114
+ if (thread.id === id) {
115
+ index = i
116
+ break
117
+ }
118
+ i += 1
119
+ }
120
+
121
+ return index
122
+ }
123
+
124
+ getThread<Data, CommentData>(id: string): TCollabThread<Data, CommentData> | null {
125
+ const index = this.getThreadIndex(id)
126
+
127
+ if (index === null) {
128
+ return null
129
+ }
130
+
131
+ return this.getYThreads().get(index).toJSON() as TCollabThread<Data, CommentData>
132
+ }
133
+
134
+ private getYThread(id: string) {
135
+ const index = this.getThreadIndex(id)
136
+
137
+ if (index === null) {
138
+ return null
139
+ }
140
+
141
+ return this.getYThreads().get(index)
142
+ }
143
+
144
+ createThread(data: TCollabThread) {
145
+ const thread = new Y.Map()
146
+ thread.set('id', uuidv4())
147
+ thread.set('createdAt', (new Date()).toISOString())
148
+ thread.set('comments', new Y.Array())
149
+
150
+ this.getYThreads().push([thread])
151
+ return this.updateThread(String(thread.get('id')), data)
152
+ }
153
+
154
+ updateThread(id: TCollabThread['id'], data: Pick<TCollabThread, 'data'>) {
155
+ const thread = this.getYThread(id)
156
+
157
+ if (thread === null) {
158
+ return null
159
+ }
160
+
161
+ thread.set('updatedAt', (new Date()).toISOString())
162
+ thread.set('data', data.data)
163
+
164
+ return thread.toJSON() as TCollabThread
165
+ }
166
+
167
+ deleteThread(id: TCollabThread['id']) {
168
+ const index = this.getThreadIndex(id)
169
+
170
+ if (index === null) {
171
+ return
172
+ }
173
+
174
+ this.getYThreads().delete(index, 1)
175
+ }
176
+
177
+ getThreadComments(threadId: TCollabThread['id']): TCollabComment[] | null {
178
+ const index = this.getThreadIndex(threadId)
179
+
180
+ if (index === null) {
181
+ return null
182
+ }
183
+
184
+ return this.getThread(threadId)?.comments ?? []
185
+ }
186
+
187
+ getThreadComment(threadId: TCollabThread['id'], commentId: TCollabComment['id']): TCollabComment | null {
188
+ const index = this.getThreadIndex(threadId)
189
+
190
+ if (index === null) {
191
+ return null
192
+ }
193
+
194
+ return this.getThread(threadId)?.comments.find(comment => comment.id === commentId) ?? null
195
+ }
196
+
197
+ addComment(threadId: TCollabThread['id'], data: TCollabComment) {
198
+ const thread = this.getYThread(threadId)
199
+
200
+ if (thread === null) return null
201
+
202
+ const commentMap = new Y.Map()
203
+ commentMap.set('id', uuidv4())
204
+ commentMap.set('createdAt', (new Date()).toISOString())
205
+ thread.get('comments').push([commentMap])
206
+
207
+ this.updateComment(threadId, String(commentMap.get('id')), data)
208
+
209
+ return thread.toJSON() as TCollabThread
210
+ }
211
+
212
+ updateComment(threadId: TCollabThread['id'], commentId: TCollabComment['id'], data: TCollabComment) {
213
+ const thread = this.getYThread(threadId)
214
+
215
+ if (thread === null) return null
216
+
217
+ let comment = null
218
+ // eslint-disable-next-line no-restricted-syntax
219
+ for (const c of thread.get('comments')) {
220
+ if (c.get('id') === commentId) {
221
+ comment = c
222
+ break
223
+ }
224
+ }
225
+
226
+ if (comment === null) return null
227
+
228
+ comment.set('updatedAt', (new Date()).toISOString())
229
+ comment.set('data', data.data)
230
+ comment.set('content', data.content)
231
+
232
+ return thread.toJSON() as TCollabThread
233
+ }
234
+
235
+ deleteComment(threadId: TCollabThread['id'], commentId: TCollabComment['id']) {
236
+ const thread = this.getYThread(threadId)
237
+
238
+ if (thread === null) return null
239
+
240
+ let commentIndex = 0
241
+ // eslint-disable-next-line no-restricted-syntax
242
+ for (const c of thread.get('comments')) {
243
+ if (c.get('id') === commentId) {
244
+ break
245
+ }
246
+ commentIndex += 1
247
+ }
248
+
249
+ if (commentIndex >= 0) {
250
+ thread.get('comments').delete(commentIndex)
251
+ }
252
+
253
+ return thread.toJSON() as TCollabThread
254
+ }
255
+
256
+ watchThreads(callback: () => void) {
257
+ this.getYThreads().observeDeep(callback)
258
+ }
259
+
260
+ unwatchThreads(callback: () => void) {
261
+ this.getYThreads().unobserveDeep(callback)
262
+ }
263
+
90
264
  }
@@ -11,11 +11,16 @@ export interface AdditionalTiptapCollabProviderWebsocketConfiguration {
11
11
  /**
12
12
  * A Hocuspocus Cloud App ID, get one here: https://cloud.tiptap.dev
13
13
  */
14
- appId: string,
14
+ appId?: string,
15
+
16
+ /**
17
+ * If you are using the on-premise version of TiptapCollab, put your baseUrl here (e.g. https://collab.yourdomain.com)
18
+ */
19
+ baseUrl?: string
15
20
  }
16
21
 
17
22
  export class TiptapCollabProviderWebsocket extends HocuspocusProviderWebsocket {
18
23
  constructor(configuration: TiptapCollabProviderWebsocketConfiguration) {
19
- super({ ...configuration as HocuspocusProviderWebsocketConfiguration, url: `wss://${configuration.appId}.collab.tiptap.cloud` })
24
+ super({ ...configuration as HocuspocusProviderWebsocketConfiguration, url: configuration.baseUrl ?? `wss://${configuration.appId}.collab.tiptap.cloud` })
20
25
  }
21
26
  }
package/src/types.ts CHANGED
@@ -106,6 +106,22 @@ export type StatesArray = { clientId: number, [key: string | number]: any }[]
106
106
 
107
107
  // hocuspocus-pro types
108
108
 
109
+ export type TCollabThread<Data = any, CommentData = any> = {
110
+ id: string;
111
+ createdAt: number;
112
+ updatedAt: number;
113
+ comments: TCollabComment<CommentData>[];
114
+ data: Data
115
+ }
116
+
117
+ export type TCollabComment<Data = any> = {
118
+ id: string;
119
+ createdAt: number;
120
+ updatedAt: number;
121
+ data: Data
122
+ content: any
123
+ }
124
+
109
125
  export type THistoryVersion = {
110
126
  name?: string;
111
127
  version: number;