@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.
@@ -2451,6 +2451,8 @@ class HocuspocusProvider extends EventEmitter {
2451
2451
  forceSync: null,
2452
2452
  };
2453
2453
  this.isConnected = true;
2454
+ this.boundDocumentUpdateHandler = this.documentUpdateHandler.bind(this);
2455
+ this.boundAwarenessUpdateHandler = this.awarenessUpdateHandler.bind(this);
2454
2456
  this.boundBroadcastChannelSubscriber = this.broadcastChannelSubscriber.bind(this);
2455
2457
  this.boundPageHide = this.pageHide.bind(this);
2456
2458
  this.boundOnOpen = this.onOpen.bind(this);
@@ -2492,8 +2494,8 @@ class HocuspocusProvider extends EventEmitter {
2492
2494
  (_b = this.awareness) === null || _b === void 0 ? void 0 : _b.on('change', () => {
2493
2495
  this.emit('awarenessChange', { states: awarenessStatesToArray(this.awareness.getStates()) });
2494
2496
  });
2495
- this.document.on('update', this.documentUpdateHandler.bind(this));
2496
- (_c = this.awareness) === null || _c === void 0 ? void 0 : _c.on('update', this.awarenessUpdateHandler.bind(this));
2497
+ this.document.on('update', this.boundDocumentUpdateHandler);
2498
+ (_c = this.awareness) === null || _c === void 0 ? void 0 : _c.on('update', this.boundAwarenessUpdateHandler);
2497
2499
  this.registerEventListeners();
2498
2500
  if (this.configuration.forceSyncInterval
2499
2501
  && typeof this.configuration.forceSyncInterval === 'number') {
@@ -2684,10 +2686,10 @@ class HocuspocusProvider extends EventEmitter {
2684
2686
  }
2685
2687
  if (this.awareness) {
2686
2688
  removeAwarenessStates(this.awareness, [this.document.clientID], 'provider destroy');
2687
- this.awareness.off('update', this.awarenessUpdateHandler);
2689
+ this.awareness.off('update', this.boundAwarenessUpdateHandler);
2688
2690
  this.awareness.destroy();
2689
2691
  }
2690
- this.document.off('update', this.documentUpdateHandler);
2692
+ this.document.off('update', this.boundDocumentUpdateHandler);
2691
2693
  this.removeAllListeners();
2692
2694
  this.configuration.websocketProvider.off('connect', this.configuration.onConnect);
2693
2695
  this.configuration.websocketProvider.off('connect', this.forwardConnect);
@@ -2818,6 +2820,17 @@ class TiptapCollabProviderWebsocket extends HocuspocusProviderWebsocket {
2818
2820
  }
2819
2821
  }
2820
2822
 
2823
+ const defaultDeleteCommentOptions = {
2824
+ deleteContent: false,
2825
+ deleteThread: false,
2826
+ };
2827
+ const defaultGetThreadsOptions = {
2828
+ types: ['unarchived'],
2829
+ };
2830
+ const defaultDeleteThreadOptions = {
2831
+ deleteComments: false,
2832
+ force: false,
2833
+ };
2821
2834
  class TiptapCollabProvider extends HocuspocusProvider {
2822
2835
  constructor(configuration) {
2823
2836
  if (!configuration.websocketProvider) {
@@ -2874,17 +2887,44 @@ class TiptapCollabProvider extends HocuspocusProvider {
2874
2887
  disableAutoVersioning() {
2875
2888
  return this.configuration.document.getMap(`${this.tiptapCollabConfigurationPrefix}config`).set('autoVersioning', 0);
2876
2889
  }
2890
+ /**
2891
+ * Returns all users in the document as Y.Map objects
2892
+ * @returns An array of Y.Map objects
2893
+ */
2877
2894
  getYThreads() {
2878
2895
  return this.configuration.document.getArray(`${this.tiptapCollabConfigurationPrefix}threads`);
2879
2896
  }
2880
- getThreads() {
2881
- return this.getYThreads().toJSON();
2897
+ /**
2898
+ * Finds all threads in the document and returns them as JSON objects
2899
+ * @options Options to control the output of the threads (e.g. include deleted threads)
2900
+ * @returns An array of threads as JSON objects
2901
+ */
2902
+ getThreads(options) {
2903
+ const { types } = { ...defaultGetThreadsOptions, ...options };
2904
+ const threads = this.getYThreads().toJSON();
2905
+ if ((types === null || types === void 0 ? void 0 : types.includes('archived')) && (types === null || types === void 0 ? void 0 : types.includes('unarchived'))) {
2906
+ return threads;
2907
+ }
2908
+ return threads.filter(currentThead => {
2909
+ if ((types === null || types === void 0 ? void 0 : types.includes('archived')) && currentThead.deletedAt) {
2910
+ return true;
2911
+ }
2912
+ if ((types === null || types === void 0 ? void 0 : types.includes('unarchived')) && !currentThead.deletedAt) {
2913
+ return true;
2914
+ }
2915
+ return false;
2916
+ });
2882
2917
  }
2918
+ /**
2919
+ * Find the index of a thread by its id
2920
+ * @param id The thread id
2921
+ * @returns The index of the thread or null if not found
2922
+ */
2883
2923
  getThreadIndex(id) {
2884
2924
  let index = null;
2885
2925
  let i = 0;
2886
2926
  // eslint-disable-next-line no-restricted-syntax
2887
- for (const thread of this.getThreads()) {
2927
+ for (const thread of this.getThreads({ types: ['archived', 'unarchived'] })) {
2888
2928
  if (thread.id === id) {
2889
2929
  index = i;
2890
2930
  break;
@@ -2893,6 +2933,11 @@ class TiptapCollabProvider extends HocuspocusProvider {
2893
2933
  }
2894
2934
  return index;
2895
2935
  }
2936
+ /**
2937
+ * Gets a single thread by its id
2938
+ * @param id The thread id
2939
+ * @returns The thread as a JSON object or null if not found
2940
+ */
2896
2941
  getThread(id) {
2897
2942
  const index = this.getThreadIndex(id);
2898
2943
  if (index === null) {
@@ -2900,6 +2945,11 @@ class TiptapCollabProvider extends HocuspocusProvider {
2900
2945
  }
2901
2946
  return this.getYThreads().get(index).toJSON();
2902
2947
  }
2948
+ /**
2949
+ * Gets a single thread by its id as a Y.Map object
2950
+ * @param id The thread id
2951
+ * @returns The thread as a Y.Map object or null if not found
2952
+ */
2903
2953
  getYThread(id) {
2904
2954
  const index = this.getThreadIndex(id);
2905
2955
  if (index === null) {
@@ -2907,6 +2957,11 @@ class TiptapCollabProvider extends HocuspocusProvider {
2907
2957
  }
2908
2958
  return this.getYThreads().get(index);
2909
2959
  }
2960
+ /**
2961
+ * Create a new thread
2962
+ * @param data The thread data
2963
+ * @returns The created thread
2964
+ */
2910
2965
  createThread(data) {
2911
2966
  let createdThread = {};
2912
2967
  this.document.transact(() => {
@@ -2914,11 +2969,19 @@ class TiptapCollabProvider extends HocuspocusProvider {
2914
2969
  thread.set('id', uuidv4());
2915
2970
  thread.set('createdAt', (new Date()).toISOString());
2916
2971
  thread.set('comments', new Y.Array());
2972
+ thread.set('deletedComments', new Y.Array());
2973
+ thread.set('deletedAt', null);
2917
2974
  this.getYThreads().push([thread]);
2918
2975
  createdThread = this.updateThread(String(thread.get('id')), data);
2919
2976
  });
2920
2977
  return createdThread;
2921
2978
  }
2979
+ /**
2980
+ * Update a specific thread
2981
+ * @param id The thread id
2982
+ * @param data New data for the thread
2983
+ * @returns The updated thread or null if the thread is not found
2984
+ */
2922
2985
  updateThread(id, data) {
2923
2986
  let updatedThread = {};
2924
2987
  this.document.transact(() => {
@@ -2937,29 +3000,87 @@ class TiptapCollabProvider extends HocuspocusProvider {
2937
3000
  });
2938
3001
  return updatedThread;
2939
3002
  }
2940
- deleteThread(id) {
3003
+ /**
3004
+ * Handle the deletion of a thread. By default, the thread and it's comments are not deleted, but marked as deleted
3005
+ * via the `deletedAt` property. Forceful deletion can be enabled by setting the `force` option to `true`.
3006
+ *
3007
+ * If you only want to delete the comments of a thread, you can set the `deleteComments` option to `true`.
3008
+ * @param id The thread id
3009
+ * @param options A set of options that control how the thread is deleted
3010
+ * @returns The deleted thread or null if the thread is not found
3011
+ */
3012
+ deleteThread(id, options) {
3013
+ const { deleteComments, force } = { ...defaultDeleteThreadOptions, ...options };
2941
3014
  const index = this.getThreadIndex(id);
2942
3015
  if (index === null) {
3016
+ return null;
3017
+ }
3018
+ if (force) {
3019
+ this.getYThreads().delete(index, 1);
2943
3020
  return;
2944
3021
  }
2945
- this.getYThreads().delete(index, 1);
3022
+ const thread = this.getYThreads().get(index);
3023
+ thread.set('deletedAt', (new Date()).toISOString());
3024
+ if (deleteComments) {
3025
+ thread.set('comments', new Y.Array());
3026
+ thread.set('deletedComments', new Y.Array());
3027
+ }
3028
+ return thread.toJSON();
3029
+ }
3030
+ /**
3031
+ * Tries to restore a deleted thread
3032
+ * @param id The thread id
3033
+ * @returns The restored thread or null if the thread is not found
3034
+ */
3035
+ restoreThread(id) {
3036
+ const index = this.getThreadIndex(id);
3037
+ if (index === null) {
3038
+ return null;
3039
+ }
3040
+ const thread = this.getYThreads().get(index);
3041
+ thread.set('deletedAt', null);
3042
+ return thread.toJSON();
2946
3043
  }
2947
- getThreadComments(threadId) {
2948
- var _a, _b;
3044
+ /**
3045
+ * Returns comments from a thread, either deleted or not
3046
+ * @param threadId The thread id
3047
+ * @param includeDeleted If you want to include deleted comments, defaults to `false`
3048
+ * @returns The comments or null if the thread is not found
3049
+ */
3050
+ getThreadComments(threadId, includeDeleted) {
3051
+ var _a, _b, _c;
2949
3052
  const index = this.getThreadIndex(threadId);
2950
3053
  if (index === null) {
2951
3054
  return null;
2952
3055
  }
2953
- return (_b = (_a = this.getThread(threadId)) === null || _a === void 0 ? void 0 : _a.comments) !== null && _b !== void 0 ? _b : [];
3056
+ 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) => {
3057
+ return a.createdAt.localeCompare(b.createdAt);
3058
+ });
3059
+ return comments !== null && comments !== void 0 ? comments : [];
2954
3060
  }
2955
- getThreadComment(threadId, commentId) {
2956
- var _a, _b;
3061
+ /**
3062
+ * Get a single comment from a specific thread
3063
+ * @param threadId The thread id
3064
+ * @param commentId The comment id
3065
+ * @param includeDeleted If you want to include deleted comments in the search
3066
+ * @returns The comment or null if not found
3067
+ */
3068
+ getThreadComment(threadId, commentId, includeDeleted) {
3069
+ var _a;
2957
3070
  const index = this.getThreadIndex(threadId);
2958
3071
  if (index === null) {
2959
3072
  return null;
2960
3073
  }
2961
- 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;
3074
+ const comments = this.getThreadComments(threadId, includeDeleted);
3075
+ return (_a = comments === null || comments === void 0 ? void 0 : comments.find(comment => comment.id === commentId)) !== null && _a !== void 0 ? _a : null;
2962
3076
  }
3077
+ /**
3078
+ * Adds a comment to a thread
3079
+ * @param threadId The thread id
3080
+ * @param data The comment data
3081
+ * @returns The updated thread or null if the thread is not found
3082
+ * @example addComment('123', { content: 'Hello world', data: { author: 'Maria Doe' } })
3083
+ */
2963
3084
  addComment(threadId, data) {
2964
3085
  let updatedThread = {};
2965
3086
  this.document.transact(() => {
@@ -2975,6 +3096,14 @@ class TiptapCollabProvider extends HocuspocusProvider {
2975
3096
  });
2976
3097
  return updatedThread;
2977
3098
  }
3099
+ /**
3100
+ * Update a comment in a thread
3101
+ * @param threadId The thread id
3102
+ * @param commentId The comment id
3103
+ * @param data The new comment data
3104
+ * @returns The updated thread or null if the thread or comment is not found
3105
+ * @example updateComment('123', { content: 'The new content', data: { attachments: ['file1.jpg'] }})
3106
+ */
2978
3107
  updateComment(threadId, commentId, data) {
2979
3108
  let updatedThread = {};
2980
3109
  this.document.transact(() => {
@@ -3002,7 +3131,15 @@ class TiptapCollabProvider extends HocuspocusProvider {
3002
3131
  });
3003
3132
  return updatedThread;
3004
3133
  }
3005
- deleteComment(threadId, commentId) {
3134
+ /**
3135
+ * Deletes a comment from a thread
3136
+ * @param threadId The thread id
3137
+ * @param commentId The comment id
3138
+ * @param options A set of options that control how the comment is deleted
3139
+ * @returns The updated thread or null if the thread or comment is not found
3140
+ */
3141
+ deleteComment(threadId, commentId, options) {
3142
+ const { deleteContent, deleteThread } = { ...defaultDeleteCommentOptions, ...options };
3006
3143
  const thread = this.getYThread(threadId);
3007
3144
  if (thread === null)
3008
3145
  return null;
@@ -3016,18 +3153,33 @@ class TiptapCollabProvider extends HocuspocusProvider {
3016
3153
  }
3017
3154
  // if the first comment of a thread is deleted we also
3018
3155
  // delete the thread itself as the source comment is gone
3019
- if (commentIndex === 0) {
3156
+ if (commentIndex === 0 && (deleteThread || this.configuration.deleteThreadOnFirstCommentDelete)) {
3020
3157
  this.deleteThread(threadId);
3021
3158
  return;
3022
3159
  }
3023
- if (commentIndex > 0) {
3024
- thread.get('comments').delete(commentIndex);
3025
- }
3160
+ const comment = thread.get('comments').get(commentIndex);
3161
+ const newComment = new Y.Map();
3162
+ newComment.set('id', comment.get('id'));
3163
+ newComment.set('createdAt', comment.get('createdAt'));
3164
+ newComment.set('updatedAt', (new Date()).toISOString());
3165
+ newComment.set('deletedAt', (new Date()).toISOString());
3166
+ newComment.set('data', comment.get('data'));
3167
+ newComment.set('content', deleteContent ? null : comment.get('content'));
3168
+ thread.get('deletedComments').push([newComment]);
3169
+ thread.get('comments').delete(commentIndex);
3026
3170
  return thread.toJSON();
3027
3171
  }
3172
+ /**
3173
+ * Start watching threads for changes
3174
+ * @param callback The callback function to be called when a thread changes
3175
+ */
3028
3176
  watchThreads(callback) {
3029
3177
  this.getYThreads().observeDeep(callback);
3030
3178
  }
3179
+ /**
3180
+ * Stop watching threads for changes
3181
+ * @param callback The callback function to be removed
3182
+ */
3031
3183
  unwatchThreads(callback) {
3032
3184
  this.getYThreads().unobserveDeep(callback);
3033
3185
  }