@hocuspocus/provider
Advanced tools
Comparing version 2.13.7 to 2.14.0
@@ -91,2 +91,4 @@ import * as mutex from 'lib0/mutex'; | ||
constructor(configuration: HocuspocusProviderConfiguration); | ||
boundDocumentUpdateHandler: (update: Uint8Array, origin: any) => void; | ||
boundAwarenessUpdateHandler: ({ added, updated, removed }: any, origin: any) => void; | ||
boundBroadcastChannelSubscriber: (data: ArrayBuffer) => void; | ||
@@ -93,0 +95,0 @@ boundPageHide: () => void; |
@@ -5,4 +5,9 @@ import type { AbstractType, YArrayEvent } from 'yjs'; | ||
import { TiptapCollabProviderWebsocket } from './TiptapCollabProviderWebsocket.js'; | ||
import type { TCollabComment, TCollabThread, THistoryVersion } from './types.js'; | ||
export type TiptapCollabProviderConfiguration = Required<Pick<HocuspocusProviderConfiguration, 'name'>> & Partial<HocuspocusProviderConfiguration> & (Required<Pick<AdditionalTiptapCollabProviderConfiguration, 'websocketProvider'>> | Required<Pick<AdditionalTiptapCollabProviderConfiguration, 'appId'>> | Required<Pick<AdditionalTiptapCollabProviderConfiguration, 'baseUrl'>>) & Pick<AdditionalTiptapCollabProviderConfiguration, 'user'>; | ||
import type { DeleteCommentOptions, TCollabComment, TCollabThread, THistoryVersion } from './types.js'; | ||
export type TiptapCollabProviderConfiguration = Required<Pick<HocuspocusProviderConfiguration, 'name'>> & Partial<HocuspocusProviderConfiguration> & (Required<Pick<AdditionalTiptapCollabProviderConfiguration, 'websocketProvider'>> | Required<Pick<AdditionalTiptapCollabProviderConfiguration, 'appId'>> | Required<Pick<AdditionalTiptapCollabProviderConfiguration, 'baseUrl'>>) & Pick<AdditionalTiptapCollabProviderConfiguration, 'user'> & { | ||
/** | ||
* Pass `true` if you want to delete a thread when the first comment is deleted. | ||
*/ | ||
deleteThreadOnFirstCommentDelete?: boolean; | ||
}; | ||
export interface AdditionalTiptapCollabProviderConfiguration { | ||
@@ -47,19 +52,101 @@ /** | ||
disableAutoVersioning(): 0; | ||
/** | ||
* Returns all users in the document as Y.Map objects | ||
* @returns An array of Y.Map objects | ||
*/ | ||
private getYThreads; | ||
/** | ||
* Finds all threads in the document and returns them as JSON objects | ||
* @returns An array of threads as JSON objects | ||
*/ | ||
getThreads<Data, CommentData>(): TCollabThread<Data, CommentData>[]; | ||
/** | ||
* Find the index of a thread by its id | ||
* @param id The thread id | ||
* @returns The index of the thread or null if not found | ||
*/ | ||
private getThreadIndex; | ||
/** | ||
* Gets a single thread by its id | ||
* @param id The thread id | ||
* @returns The thread as a JSON object or null if not found | ||
*/ | ||
getThread<Data, CommentData>(id: string): TCollabThread<Data, CommentData> | null; | ||
/** | ||
* Gets a single thread by its id as a Y.Map object | ||
* @param id The thread id | ||
* @returns The thread as a Y.Map object or null if not found | ||
*/ | ||
private getYThread; | ||
createThread(data: Omit<TCollabThread, 'id' | 'createdAt' | 'updatedAt' | 'comments'>): TCollabThread; | ||
/** | ||
* Create a new thread | ||
* @param data The thread data | ||
* @returns The created thread | ||
*/ | ||
createThread(data: Omit<TCollabThread, 'id' | 'createdAt' | 'updatedAt' | 'comments' | 'deletedComments'>): TCollabThread; | ||
/** | ||
* Update a specific thread | ||
* @param id The thread id | ||
* @param data New data for the thread | ||
* @returns The updated thread or null if the thread is not found | ||
*/ | ||
updateThread(id: TCollabThread['id'], data: Partial<Pick<TCollabThread, 'data'> & { | ||
resolvedAt: TCollabThread['resolvedAt'] | null; | ||
}>): TCollabThread; | ||
/** | ||
* Delete a specific thread and all its comments | ||
* @param id The thread id | ||
* @returns void | ||
*/ | ||
deleteThread(id: TCollabThread['id']): void; | ||
getThreadComments(threadId: TCollabThread['id']): TCollabComment[] | null; | ||
getThreadComment(threadId: TCollabThread['id'], commentId: TCollabComment['id']): TCollabComment | null; | ||
/** | ||
* Returns comments from a thread, either deleted or not | ||
* @param threadId The thread id | ||
* @param includeDeleted If you want to include deleted comments, defaults to `false` | ||
* @returns The comments or null if the thread is not found | ||
*/ | ||
getThreadComments(threadId: TCollabThread['id'], includeDeleted?: boolean): TCollabComment[] | null; | ||
/** | ||
* Get a single comment from a specific thread | ||
* @param threadId The thread id | ||
* @param commentId The comment id | ||
* @param includeDeleted If you want to include deleted comments in the search | ||
* @returns The comment or null if not found | ||
*/ | ||
getThreadComment(threadId: TCollabThread['id'], commentId: TCollabComment['id'], includeDeleted?: boolean): TCollabComment | null; | ||
/** | ||
* Adds a comment to a thread | ||
* @param threadId The thread id | ||
* @param data The comment data | ||
* @returns The updated thread or null if the thread is not found | ||
* @example addComment('123', { content: 'Hello world', data: { author: 'Maria Doe' } }) | ||
*/ | ||
addComment(threadId: TCollabThread['id'], data: Omit<TCollabComment, 'id' | 'updatedAt' | 'createdAt'>): TCollabThread; | ||
/** | ||
* Update a comment in a thread | ||
* @param threadId The thread id | ||
* @param commentId The comment id | ||
* @param data The new comment data | ||
* @returns The updated thread or null if the thread or comment is not found | ||
* @example updateComment('123', { content: 'The new content', data: { attachments: ['file1.jpg'] }}) | ||
*/ | ||
updateComment(threadId: TCollabThread['id'], commentId: TCollabComment['id'], data: Partial<Pick<TCollabComment, 'data' | 'content'>>): TCollabThread; | ||
deleteComment(threadId: TCollabThread['id'], commentId: TCollabComment['id']): TCollabThread | null | undefined; | ||
/** | ||
* Deletes a comment from a thread | ||
* @param threadId The thread id | ||
* @param commentId The comment id | ||
* @param options A set of options that control how the comment is deleted | ||
* @returns The updated thread or null if the thread or comment is not found | ||
*/ | ||
deleteComment(threadId: TCollabThread['id'], commentId: TCollabComment['id'], options: DeleteCommentOptions): TCollabThread | null | undefined; | ||
/** | ||
* Start watching threads for changes | ||
* @param callback The callback function to be called when a thread changes | ||
*/ | ||
watchThreads(callback: () => void): void; | ||
/** | ||
* Stop watching threads for changes | ||
* @param callback The callback function to be removed | ||
*/ | ||
unwatchThreads(callback: () => void): void; | ||
} |
@@ -93,2 +93,3 @@ import { Encoder } from 'lib0/encoding'; | ||
comments: TCollabComment<CommentData>[]; | ||
deletedComments: TCollabComment<CommentData>[]; | ||
data: Data; | ||
@@ -98,4 +99,5 @@ }; | ||
id: string; | ||
createdAt: number; | ||
updatedAt: number; | ||
createdAt: string; | ||
updatedAt: string; | ||
deletedAt?: string; | ||
data: Data; | ||
@@ -149,1 +151,11 @@ content: any; | ||
}; | ||
export type DeleteCommentOptions = { | ||
/** | ||
* If `true`, the thread will also be deleted if the deleted comment was the first comment in the thread. | ||
*/ | ||
deleteThread?: boolean; | ||
/** | ||
* If `true`, will remove the content of the deleted comment | ||
*/ | ||
deleteContent?: boolean; | ||
}; |
{ | ||
"name": "@hocuspocus/provider", | ||
"version": "2.13.7", | ||
"version": "2.14.0", | ||
"description": "hocuspocus provider", | ||
@@ -32,3 +32,3 @@ "homepage": "https://hocuspocus.dev", | ||
"dependencies": { | ||
"@hocuspocus/common": "^2.13.7", | ||
"@hocuspocus/common": "^2.14.0", | ||
"@lifeomic/attempt": "^3.0.2", | ||
@@ -35,0 +35,0 @@ "lib0": "^0.2.87", |
@@ -215,4 +215,4 @@ import { awarenessStatesToArray } from '@hocuspocus/common' | ||
this.document.on('update', this.documentUpdateHandler.bind(this)) | ||
this.awareness?.on('update', this.awarenessUpdateHandler.bind(this)) | ||
this.document.on('update', this.boundDocumentUpdateHandler) | ||
this.awareness?.on('update', this.boundAwarenessUpdateHandler) | ||
this.registerEventListeners() | ||
@@ -233,2 +233,6 @@ | ||
boundDocumentUpdateHandler = this.documentUpdateHandler.bind(this) | ||
boundAwarenessUpdateHandler = this.awarenessUpdateHandler.bind(this) | ||
boundBroadcastChannelSubscriber = this.broadcastChannelSubscriber.bind(this) | ||
@@ -495,7 +499,7 @@ | ||
removeAwarenessStates(this.awareness, [this.document.clientID], 'provider destroy') | ||
this.awareness.off('update', this.awarenessUpdateHandler) | ||
this.awareness.off('update', this.boundAwarenessUpdateHandler) | ||
this.awareness.destroy() | ||
} | ||
this.document.off('update', this.documentUpdateHandler) | ||
this.document.off('update', this.boundDocumentUpdateHandler) | ||
@@ -502,0 +506,0 @@ this.removeAllListeners() |
@@ -11,5 +11,11 @@ import type { AbstractType, YArrayEvent } from 'yjs' | ||
import type { | ||
DeleteCommentOptions, | ||
TCollabComment, TCollabThread, THistoryVersion, | ||
} from './types.js' | ||
const defaultDeleteCommentOptions: DeleteCommentOptions = { | ||
deleteContent: false, | ||
deleteThread: false, | ||
} | ||
export type TiptapCollabProviderConfiguration = | ||
@@ -21,3 +27,8 @@ Required<Pick<HocuspocusProviderConfiguration, 'name'>> & | ||
Required<Pick<AdditionalTiptapCollabProviderConfiguration, 'baseUrl'>>) & | ||
Pick<AdditionalTiptapCollabProviderConfiguration, 'user'> | ||
Pick<AdditionalTiptapCollabProviderConfiguration, 'user'> & { | ||
/** | ||
* Pass `true` if you want to delete a thread when the first comment is deleted. | ||
*/ | ||
deleteThreadOnFirstCommentDelete?: boolean, | ||
} | ||
@@ -112,2 +123,6 @@ export interface AdditionalTiptapCollabProviderConfiguration { | ||
/** | ||
* Returns all users in the document as Y.Map objects | ||
* @returns An array of Y.Map objects | ||
*/ | ||
private getYThreads() { | ||
@@ -117,2 +132,6 @@ return this.configuration.document.getArray<Y.Map<any>>(`${this.tiptapCollabConfigurationPrefix}threads`) | ||
/** | ||
* Finds all threads in the document and returns them as JSON objects | ||
* @returns An array of threads as JSON objects | ||
*/ | ||
getThreads<Data, CommentData>(): TCollabThread<Data, CommentData>[] { | ||
@@ -122,2 +141,7 @@ return this.getYThreads().toJSON() as TCollabThread<Data, CommentData>[] | ||
/** | ||
* Find the index of a thread by its id | ||
* @param id The thread id | ||
* @returns The index of the thread or null if not found | ||
*/ | ||
private getThreadIndex(id: string): number | null { | ||
@@ -139,2 +163,7 @@ let index = null | ||
/** | ||
* Gets a single thread by its id | ||
* @param id The thread id | ||
* @returns The thread as a JSON object or null if not found | ||
*/ | ||
getThread<Data, CommentData>(id: string): TCollabThread<Data, CommentData> | null { | ||
@@ -150,2 +179,7 @@ const index = this.getThreadIndex(id) | ||
/** | ||
* Gets a single thread by its id as a Y.Map object | ||
* @param id The thread id | ||
* @returns The thread as a Y.Map object or null if not found | ||
*/ | ||
private getYThread(id: string) { | ||
@@ -161,3 +195,8 @@ const index = this.getThreadIndex(id) | ||
createThread(data: Omit<TCollabThread, 'id' | 'createdAt' | 'updatedAt' | 'comments'>) { | ||
/** | ||
* Create a new thread | ||
* @param data The thread data | ||
* @returns The created thread | ||
*/ | ||
createThread(data: Omit<TCollabThread, 'id' | 'createdAt' | 'updatedAt' | 'comments' | 'deletedComments'>) { | ||
let createdThread: TCollabThread = {} as TCollabThread | ||
@@ -170,2 +209,3 @@ | ||
thread.set('comments', new Y.Array()) | ||
thread.set('deletedComments', new Y.Array()) | ||
@@ -179,2 +219,8 @@ this.getYThreads().push([thread]) | ||
/** | ||
* Update a specific thread | ||
* @param id The thread id | ||
* @param data New data for the thread | ||
* @returns The updated thread or null if the thread is not found | ||
*/ | ||
updateThread(id: TCollabThread['id'], data: Partial<Pick<TCollabThread, 'data'> & { | ||
@@ -208,2 +254,7 @@ resolvedAt: TCollabThread['resolvedAt'] | null | ||
/** | ||
* Delete a specific thread and all its comments | ||
* @param id The thread id | ||
* @returns void | ||
*/ | ||
deleteThread(id: TCollabThread['id']) { | ||
@@ -219,3 +270,9 @@ const index = this.getThreadIndex(id) | ||
getThreadComments(threadId: TCollabThread['id']): TCollabComment[] | null { | ||
/** | ||
* Returns comments from a thread, either deleted or not | ||
* @param threadId The thread id | ||
* @param includeDeleted If you want to include deleted comments, defaults to `false` | ||
* @returns The comments or null if the thread is not found | ||
*/ | ||
getThreadComments(threadId: TCollabThread['id'], includeDeleted?: boolean): TCollabComment[] | null { | ||
const index = this.getThreadIndex(threadId) | ||
@@ -227,6 +284,17 @@ | ||
return this.getThread(threadId)?.comments ?? [] | ||
const comments = !includeDeleted ? this.getThread(threadId)?.comments : [...(this.getThread(threadId)?.comments || []), ...(this.getThread(threadId)?.deletedComments || [])].sort((a, b) => { | ||
return a.createdAt.localeCompare(b.createdAt) | ||
}) | ||
return comments ?? [] | ||
} | ||
getThreadComment(threadId: TCollabThread['id'], commentId: TCollabComment['id']): TCollabComment | null { | ||
/** | ||
* Get a single comment from a specific thread | ||
* @param threadId The thread id | ||
* @param commentId The comment id | ||
* @param includeDeleted If you want to include deleted comments in the search | ||
* @returns The comment or null if not found | ||
*/ | ||
getThreadComment(threadId: TCollabThread['id'], commentId: TCollabComment['id'], includeDeleted?: boolean): TCollabComment | null { | ||
const index = this.getThreadIndex(threadId) | ||
@@ -238,5 +306,14 @@ | ||
return this.getThread(threadId)?.comments.find(comment => comment.id === commentId) ?? null | ||
const comments = this.getThreadComments(threadId, includeDeleted) | ||
return comments?.find(comment => comment.id === commentId) ?? null | ||
} | ||
/** | ||
* Adds a comment to a thread | ||
* @param threadId The thread id | ||
* @param data The comment data | ||
* @returns The updated thread or null if the thread is not found | ||
* @example addComment('123', { content: 'Hello world', data: { author: 'Maria Doe' } }) | ||
*/ | ||
addComment(threadId: TCollabThread['id'], data: Omit<TCollabComment, 'id' | 'updatedAt' | 'createdAt'>) { | ||
@@ -263,2 +340,10 @@ let updatedThread: TCollabThread = {} as TCollabThread | ||
/** | ||
* Update a comment in a thread | ||
* @param threadId The thread id | ||
* @param commentId The comment id | ||
* @param data The new comment data | ||
* @returns The updated thread or null if the thread or comment is not found | ||
* @example updateComment('123', { content: 'The new content', data: { attachments: ['file1.jpg'] }}) | ||
*/ | ||
updateComment(threadId: TCollabThread['id'], commentId: TCollabComment['id'], data: Partial<Pick<TCollabComment, 'data' | 'content'>>) { | ||
@@ -299,3 +384,12 @@ let updatedThread: TCollabThread = {} as TCollabThread | ||
deleteComment(threadId: TCollabThread['id'], commentId: TCollabComment['id']) { | ||
/** | ||
* Deletes a comment from a thread | ||
* @param threadId The thread id | ||
* @param commentId The comment id | ||
* @param options A set of options that control how the comment is deleted | ||
* @returns The updated thread or null if the thread or comment is not found | ||
*/ | ||
deleteComment(threadId: TCollabThread['id'], commentId: TCollabComment['id'], options: DeleteCommentOptions) { | ||
const { deleteContent, deleteThread } = { ...defaultDeleteCommentOptions, ...options } | ||
const thread = this.getYThread(threadId) | ||
@@ -316,3 +410,3 @@ | ||
// delete the thread itself as the source comment is gone | ||
if (commentIndex === 0) { | ||
if (commentIndex === 0 && (deleteThread || (this.configuration as TiptapCollabProviderConfiguration).deleteThreadOnFirstCommentDelete)) { | ||
this.deleteThread(threadId) | ||
@@ -322,9 +416,22 @@ return | ||
if (commentIndex > 0) { | ||
thread.get('comments').delete(commentIndex) | ||
} | ||
const comment = thread.get('comments').get(commentIndex) | ||
const newComment = new Y.Map() | ||
newComment.set('id', comment.get('id')) | ||
newComment.set('createdAt', comment.get('createdAt')) | ||
newComment.set('updatedAt', (new Date()).toISOString()) | ||
newComment.set('deletedAt', (new Date()).toISOString()) | ||
newComment.set('data', comment.get('data')) | ||
newComment.set('content', deleteContent ? null : comment.get('content')) | ||
thread.get('deletedComments').push([newComment]) | ||
thread.get('comments').delete(commentIndex) | ||
return thread.toJSON() as TCollabThread | ||
} | ||
/** | ||
* Start watching threads for changes | ||
* @param callback The callback function to be called when a thread changes | ||
*/ | ||
watchThreads(callback: () => void) { | ||
@@ -334,2 +441,6 @@ this.getYThreads().observeDeep(callback) | ||
/** | ||
* Stop watching threads for changes | ||
* @param callback The callback function to be removed | ||
*/ | ||
unwatchThreads(callback: () => void) { | ||
@@ -336,0 +447,0 @@ this.getYThreads().unobserveDeep(callback) |
@@ -115,2 +115,3 @@ import { Encoder } from 'lib0/encoding' | ||
comments: TCollabComment<CommentData>[]; | ||
deletedComments: TCollabComment<CommentData>[]; | ||
data: Data | ||
@@ -121,4 +122,5 @@ } | ||
id: string; | ||
createdAt: number; | ||
updatedAt: number; | ||
createdAt: string; | ||
updatedAt: string; | ||
deletedAt?: string; | ||
data: Data | ||
@@ -188,1 +190,13 @@ content: any | ||
}; | ||
export type DeleteCommentOptions = { | ||
/** | ||
* If `true`, the thread will also be deleted if the deleted comment was the first comment in the thread. | ||
*/ | ||
deleteThread?: boolean | ||
/** | ||
* If `true`, will remove the content of the deleted comment | ||
*/ | ||
deleteContent?: boolean | ||
} |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
853988
9829
Updated@hocuspocus/common@^2.14.0