@hocuspocus/provider 2.13.7 → 2.15.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.
@@ -2475,6 +2475,8 @@ class HocuspocusProvider extends EventEmitter {
2475
2475
  forceSync: null,
2476
2476
  };
2477
2477
  this.isConnected = true;
2478
+ this.boundDocumentUpdateHandler = this.documentUpdateHandler.bind(this);
2479
+ this.boundAwarenessUpdateHandler = this.awarenessUpdateHandler.bind(this);
2478
2480
  this.boundBroadcastChannelSubscriber = this.broadcastChannelSubscriber.bind(this);
2479
2481
  this.boundPageHide = this.pageHide.bind(this);
2480
2482
  this.boundOnOpen = this.onOpen.bind(this);
@@ -2516,8 +2518,8 @@ class HocuspocusProvider extends EventEmitter {
2516
2518
  (_b = this.awareness) === null || _b === void 0 ? void 0 : _b.on('change', () => {
2517
2519
  this.emit('awarenessChange', { states: common.awarenessStatesToArray(this.awareness.getStates()) });
2518
2520
  });
2519
- this.document.on('update', this.documentUpdateHandler.bind(this));
2520
- (_c = this.awareness) === null || _c === void 0 ? void 0 : _c.on('update', this.awarenessUpdateHandler.bind(this));
2521
+ this.document.on('update', this.boundDocumentUpdateHandler);
2522
+ (_c = this.awareness) === null || _c === void 0 ? void 0 : _c.on('update', this.boundAwarenessUpdateHandler);
2521
2523
  this.registerEventListeners();
2522
2524
  if (this.configuration.forceSyncInterval
2523
2525
  && typeof this.configuration.forceSyncInterval === 'number') {
@@ -2708,10 +2710,10 @@ class HocuspocusProvider extends EventEmitter {
2708
2710
  }
2709
2711
  if (this.awareness) {
2710
2712
  removeAwarenessStates(this.awareness, [this.document.clientID], 'provider destroy');
2711
- this.awareness.off('update', this.awarenessUpdateHandler);
2713
+ this.awareness.off('update', this.boundAwarenessUpdateHandler);
2712
2714
  this.awareness.destroy();
2713
2715
  }
2714
- this.document.off('update', this.documentUpdateHandler);
2716
+ this.document.off('update', this.boundDocumentUpdateHandler);
2715
2717
  this.removeAllListeners();
2716
2718
  this.configuration.websocketProvider.off('connect', this.configuration.onConnect);
2717
2719
  this.configuration.websocketProvider.off('connect', this.forwardConnect);
@@ -2842,6 +2844,17 @@ class TiptapCollabProviderWebsocket extends HocuspocusProviderWebsocket {
2842
2844
  }
2843
2845
  }
2844
2846
 
2847
+ const defaultDeleteCommentOptions = {
2848
+ deleteContent: false,
2849
+ deleteThread: false,
2850
+ };
2851
+ const defaultGetThreadsOptions = {
2852
+ types: ['unarchived'],
2853
+ };
2854
+ const defaultDeleteThreadOptions = {
2855
+ deleteComments: false,
2856
+ force: false,
2857
+ };
2845
2858
  class TiptapCollabProvider extends HocuspocusProvider {
2846
2859
  constructor(configuration) {
2847
2860
  if (!configuration.websocketProvider) {
@@ -2898,17 +2911,44 @@ class TiptapCollabProvider extends HocuspocusProvider {
2898
2911
  disableAutoVersioning() {
2899
2912
  return this.configuration.document.getMap(`${this.tiptapCollabConfigurationPrefix}config`).set('autoVersioning', 0);
2900
2913
  }
2914
+ /**
2915
+ * Returns all users in the document as Y.Map objects
2916
+ * @returns An array of Y.Map objects
2917
+ */
2901
2918
  getYThreads() {
2902
2919
  return this.configuration.document.getArray(`${this.tiptapCollabConfigurationPrefix}threads`);
2903
2920
  }
2904
- getThreads() {
2905
- return this.getYThreads().toJSON();
2921
+ /**
2922
+ * Finds all threads in the document and returns them as JSON objects
2923
+ * @options Options to control the output of the threads (e.g. include deleted threads)
2924
+ * @returns An array of threads as JSON objects
2925
+ */
2926
+ getThreads(options) {
2927
+ const { types } = { ...defaultGetThreadsOptions, ...options };
2928
+ const threads = this.getYThreads().toJSON();
2929
+ if ((types === null || types === void 0 ? void 0 : types.includes('archived')) && (types === null || types === void 0 ? void 0 : types.includes('unarchived'))) {
2930
+ return threads;
2931
+ }
2932
+ return threads.filter(currentThead => {
2933
+ if ((types === null || types === void 0 ? void 0 : types.includes('archived')) && currentThead.deletedAt) {
2934
+ return true;
2935
+ }
2936
+ if ((types === null || types === void 0 ? void 0 : types.includes('unarchived')) && !currentThead.deletedAt) {
2937
+ return true;
2938
+ }
2939
+ return false;
2940
+ });
2906
2941
  }
2942
+ /**
2943
+ * Find the index of a thread by its id
2944
+ * @param id The thread id
2945
+ * @returns The index of the thread or null if not found
2946
+ */
2907
2947
  getThreadIndex(id) {
2908
2948
  let index = null;
2909
2949
  let i = 0;
2910
2950
  // eslint-disable-next-line no-restricted-syntax
2911
- for (const thread of this.getThreads()) {
2951
+ for (const thread of this.getThreads({ types: ['archived', 'unarchived'] })) {
2912
2952
  if (thread.id === id) {
2913
2953
  index = i;
2914
2954
  break;
@@ -2917,6 +2957,11 @@ class TiptapCollabProvider extends HocuspocusProvider {
2917
2957
  }
2918
2958
  return index;
2919
2959
  }
2960
+ /**
2961
+ * Gets a single thread by its id
2962
+ * @param id The thread id
2963
+ * @returns The thread as a JSON object or null if not found
2964
+ */
2920
2965
  getThread(id) {
2921
2966
  const index = this.getThreadIndex(id);
2922
2967
  if (index === null) {
@@ -2924,6 +2969,11 @@ class TiptapCollabProvider extends HocuspocusProvider {
2924
2969
  }
2925
2970
  return this.getYThreads().get(index).toJSON();
2926
2971
  }
2972
+ /**
2973
+ * Gets a single thread by its id as a Y.Map object
2974
+ * @param id The thread id
2975
+ * @returns The thread as a Y.Map object or null if not found
2976
+ */
2927
2977
  getYThread(id) {
2928
2978
  const index = this.getThreadIndex(id);
2929
2979
  if (index === null) {
@@ -2931,6 +2981,11 @@ class TiptapCollabProvider extends HocuspocusProvider {
2931
2981
  }
2932
2982
  return this.getYThreads().get(index);
2933
2983
  }
2984
+ /**
2985
+ * Create a new thread
2986
+ * @param data The thread data
2987
+ * @returns The created thread
2988
+ */
2934
2989
  createThread(data) {
2935
2990
  let createdThread = {};
2936
2991
  this.document.transact(() => {
@@ -2938,11 +2993,19 @@ class TiptapCollabProvider extends HocuspocusProvider {
2938
2993
  thread.set('id', uuidv4());
2939
2994
  thread.set('createdAt', (new Date()).toISOString());
2940
2995
  thread.set('comments', new Y__namespace.Array());
2996
+ thread.set('deletedComments', new Y__namespace.Array());
2997
+ thread.set('deletedAt', null);
2941
2998
  this.getYThreads().push([thread]);
2942
2999
  createdThread = this.updateThread(String(thread.get('id')), data);
2943
3000
  });
2944
3001
  return createdThread;
2945
3002
  }
3003
+ /**
3004
+ * Update a specific thread
3005
+ * @param id The thread id
3006
+ * @param data New data for the thread
3007
+ * @returns The updated thread or null if the thread is not found
3008
+ */
2946
3009
  updateThread(id, data) {
2947
3010
  let updatedThread = {};
2948
3011
  this.document.transact(() => {
@@ -2961,29 +3024,87 @@ class TiptapCollabProvider extends HocuspocusProvider {
2961
3024
  });
2962
3025
  return updatedThread;
2963
3026
  }
2964
- deleteThread(id) {
3027
+ /**
3028
+ * Handle the deletion of a thread. By default, the thread and it's comments are not deleted, but marked as deleted
3029
+ * via the `deletedAt` property. Forceful deletion can be enabled by setting the `force` option to `true`.
3030
+ *
3031
+ * If you only want to delete the comments of a thread, you can set the `deleteComments` option to `true`.
3032
+ * @param id The thread id
3033
+ * @param options A set of options that control how the thread is deleted
3034
+ * @returns The deleted thread or null if the thread is not found
3035
+ */
3036
+ deleteThread(id, options) {
3037
+ const { deleteComments, force } = { ...defaultDeleteThreadOptions, ...options };
2965
3038
  const index = this.getThreadIndex(id);
2966
3039
  if (index === null) {
3040
+ return null;
3041
+ }
3042
+ if (force) {
3043
+ this.getYThreads().delete(index, 1);
2967
3044
  return;
2968
3045
  }
2969
- this.getYThreads().delete(index, 1);
3046
+ const thread = this.getYThreads().get(index);
3047
+ thread.set('deletedAt', (new Date()).toISOString());
3048
+ if (deleteComments) {
3049
+ thread.set('comments', new Y__namespace.Array());
3050
+ thread.set('deletedComments', new Y__namespace.Array());
3051
+ }
3052
+ return thread.toJSON();
3053
+ }
3054
+ /**
3055
+ * Tries to restore a deleted thread
3056
+ * @param id The thread id
3057
+ * @returns The restored thread or null if the thread is not found
3058
+ */
3059
+ restoreThread(id) {
3060
+ const index = this.getThreadIndex(id);
3061
+ if (index === null) {
3062
+ return null;
3063
+ }
3064
+ const thread = this.getYThreads().get(index);
3065
+ thread.set('deletedAt', null);
3066
+ return thread.toJSON();
2970
3067
  }
2971
- getThreadComments(threadId) {
2972
- var _a, _b;
3068
+ /**
3069
+ * Returns comments from a thread, either deleted or not
3070
+ * @param threadId The thread id
3071
+ * @param includeDeleted If you want to include deleted comments, defaults to `false`
3072
+ * @returns The comments or null if the thread is not found
3073
+ */
3074
+ getThreadComments(threadId, includeDeleted) {
3075
+ var _a, _b, _c;
2973
3076
  const index = this.getThreadIndex(threadId);
2974
3077
  if (index === null) {
2975
3078
  return null;
2976
3079
  }
2977
- return (_b = (_a = this.getThread(threadId)) === null || _a === void 0 ? void 0 : _a.comments) !== null && _b !== void 0 ? _b : [];
3080
+ const comments = !includeDeleted ? (_a = this.getThread(threadId)) === null || _a === void 0 ? void 0 : _a.comments : [...(((_b = this.getThread(threadId)) === null || _b === void 0 ? void 0 : _b.comments) || []), ...(((_c = this.getThread(threadId)) === null || _c === void 0 ? void 0 : _c.deletedComments) || [])].sort((a, b) => {
3081
+ return a.createdAt.localeCompare(b.createdAt);
3082
+ });
3083
+ return comments !== null && comments !== void 0 ? comments : [];
2978
3084
  }
2979
- getThreadComment(threadId, commentId) {
2980
- var _a, _b;
3085
+ /**
3086
+ * Get a single comment from a specific thread
3087
+ * @param threadId The thread id
3088
+ * @param commentId The comment id
3089
+ * @param includeDeleted If you want to include deleted comments in the search
3090
+ * @returns The comment or null if not found
3091
+ */
3092
+ getThreadComment(threadId, commentId, includeDeleted) {
3093
+ var _a;
2981
3094
  const index = this.getThreadIndex(threadId);
2982
3095
  if (index === null) {
2983
3096
  return null;
2984
3097
  }
2985
- return (_b = (_a = this.getThread(threadId)) === null || _a === void 0 ? void 0 : _a.comments.find(comment => comment.id === commentId)) !== null && _b !== void 0 ? _b : null;
3098
+ const comments = this.getThreadComments(threadId, includeDeleted);
3099
+ return (_a = comments === null || comments === void 0 ? void 0 : comments.find(comment => comment.id === commentId)) !== null && _a !== void 0 ? _a : null;
2986
3100
  }
3101
+ /**
3102
+ * Adds a comment to a thread
3103
+ * @param threadId The thread id
3104
+ * @param data The comment data
3105
+ * @returns The updated thread or null if the thread is not found
3106
+ * @example addComment('123', { content: 'Hello world', data: { author: 'Maria Doe' } })
3107
+ */
2987
3108
  addComment(threadId, data) {
2988
3109
  let updatedThread = {};
2989
3110
  this.document.transact(() => {
@@ -2999,6 +3120,14 @@ class TiptapCollabProvider extends HocuspocusProvider {
2999
3120
  });
3000
3121
  return updatedThread;
3001
3122
  }
3123
+ /**
3124
+ * Update a comment in a thread
3125
+ * @param threadId The thread id
3126
+ * @param commentId The comment id
3127
+ * @param data The new comment data
3128
+ * @returns The updated thread or null if the thread or comment is not found
3129
+ * @example updateComment('123', { content: 'The new content', data: { attachments: ['file1.jpg'] }})
3130
+ */
3002
3131
  updateComment(threadId, commentId, data) {
3003
3132
  let updatedThread = {};
3004
3133
  this.document.transact(() => {
@@ -3026,7 +3155,15 @@ class TiptapCollabProvider extends HocuspocusProvider {
3026
3155
  });
3027
3156
  return updatedThread;
3028
3157
  }
3029
- deleteComment(threadId, commentId) {
3158
+ /**
3159
+ * Deletes a comment from a thread
3160
+ * @param threadId The thread id
3161
+ * @param commentId The comment id
3162
+ * @param options A set of options that control how the comment is deleted
3163
+ * @returns The updated thread or null if the thread or comment is not found
3164
+ */
3165
+ deleteComment(threadId, commentId, options) {
3166
+ const { deleteContent, deleteThread } = { ...defaultDeleteCommentOptions, ...options };
3030
3167
  const thread = this.getYThread(threadId);
3031
3168
  if (thread === null)
3032
3169
  return null;
@@ -3040,18 +3177,33 @@ class TiptapCollabProvider extends HocuspocusProvider {
3040
3177
  }
3041
3178
  // if the first comment of a thread is deleted we also
3042
3179
  // delete the thread itself as the source comment is gone
3043
- if (commentIndex === 0) {
3180
+ if (commentIndex === 0 && (deleteThread || this.configuration.deleteThreadOnFirstCommentDelete)) {
3044
3181
  this.deleteThread(threadId);
3045
3182
  return;
3046
3183
  }
3047
- if (commentIndex > 0) {
3048
- thread.get('comments').delete(commentIndex);
3049
- }
3184
+ const comment = thread.get('comments').get(commentIndex);
3185
+ const newComment = new Y__namespace.Map();
3186
+ newComment.set('id', comment.get('id'));
3187
+ newComment.set('createdAt', comment.get('createdAt'));
3188
+ newComment.set('updatedAt', (new Date()).toISOString());
3189
+ newComment.set('deletedAt', (new Date()).toISOString());
3190
+ newComment.set('data', comment.get('data'));
3191
+ newComment.set('content', deleteContent ? null : comment.get('content'));
3192
+ thread.get('deletedComments').push([newComment]);
3193
+ thread.get('comments').delete(commentIndex);
3050
3194
  return thread.toJSON();
3051
3195
  }
3196
+ /**
3197
+ * Start watching threads for changes
3198
+ * @param callback The callback function to be called when a thread changes
3199
+ */
3052
3200
  watchThreads(callback) {
3053
3201
  this.getYThreads().observeDeep(callback);
3054
3202
  }
3203
+ /**
3204
+ * Stop watching threads for changes
3205
+ * @param callback The callback function to be removed
3206
+ */
3055
3207
  unwatchThreads(callback) {
3056
3208
  this.getYThreads().unobserveDeep(callback);
3057
3209
  }