Skip to content

Commit

Permalink
Merge pull request #885 from ueberdosis/feature/thread-deletion-tombs…
Browse files Browse the repository at this point in the history
…toning

feature(provider): allow thread tombstoning and restoration
  • Loading branch information
bdbch authored Dec 10, 2024
2 parents e96b334 + c8ba040 commit 6a71700
Show file tree
Hide file tree
Showing 3 changed files with 133 additions and 29 deletions.
38 changes: 20 additions & 18 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

92 changes: 81 additions & 11 deletions packages/provider/src/TiptapCollabProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,27 @@ import {
} from './HocuspocusProvider.js'

import { TiptapCollabProviderWebsocket } from './TiptapCollabProviderWebsocket.js'
import type {
DeleteCommentOptions,
TCollabComment, TCollabThread, THistoryVersion,
import {
type DeleteCommentOptions,
type DeleteThreadOptions,
type GetThreadsOptions,
type TCollabComment, type TCollabThread, type THistoryVersion,
} from './types.js'

const defaultDeleteCommentOptions: DeleteCommentOptions = {
deleteContent: false,
deleteThread: false,
}

const defaultGetThreadsOptions: GetThreadsOptions = {
types: ['unarchived'],
}

const defaultDeleteThreadOptions: DeleteThreadOptions = {
deleteComments: false,
force: false,
}

export type TiptapCollabProviderConfiguration =
Required<Pick<HocuspocusProviderConfiguration, 'name'>> &
Partial<HocuspocusProviderConfiguration> &
Expand Down Expand Up @@ -128,10 +139,29 @@ export class TiptapCollabProvider extends HocuspocusProvider {

/**
* Finds all threads in the document and returns them as JSON objects
* @options Options to control the output of the threads (e.g. include deleted threads)
* @returns An array of threads as JSON objects
*/
getThreads<Data, CommentData>(): TCollabThread<Data, CommentData>[] {
return this.getYThreads().toJSON() as TCollabThread<Data, CommentData>[]
getThreads<Data, CommentData>(options?: GetThreadsOptions): TCollabThread<Data, CommentData>[] {
const { types } = { ...defaultGetThreadsOptions, ...options } as GetThreadsOptions

const threads = this.getYThreads().toJSON() as TCollabThread<Data, CommentData>[]

if (types?.includes('archived') && types?.includes('unarchived')) {
return threads
}

return threads.filter(currentThead => {
if (types?.includes('archived') && currentThead.deletedAt) {
return true
}

if (types?.includes('unarchived') && !currentThead.deletedAt) {
return true
}

return false
})
}

/**
Expand All @@ -144,7 +174,7 @@ export class TiptapCollabProvider extends HocuspocusProvider {

let i = 0
// eslint-disable-next-line no-restricted-syntax
for (const thread of this.getThreads()) {
for (const thread of this.getThreads({ types: ['archived', 'unarchived'] })) {
if (thread.id === id) {
index = i
break
Expand Down Expand Up @@ -190,7 +220,7 @@ export class TiptapCollabProvider extends HocuspocusProvider {
* @param data The thread data
* @returns The created thread
*/
createThread(data: Omit<TCollabThread, 'id' | 'createdAt' | 'updatedAt' | 'comments' | 'deletedComments'>) {
createThread(data: Omit<TCollabThread, 'id' | 'createdAt' | 'updatedAt' | 'deletedAt' | 'comments' | 'deletedComments'>) {
let createdThread: TCollabThread = {} as TCollabThread

this.document.transact(() => {
Expand All @@ -199,6 +229,7 @@ export class TiptapCollabProvider extends HocuspocusProvider {
thread.set('createdAt', (new Date()).toISOString())
thread.set('comments', new Y.Array())
thread.set('deletedComments', new Y.Array())
thread.set('deletedAt', null)

this.getYThreads().push([thread])
createdThread = this.updateThread(String(thread.get('id')), data)
Expand Down Expand Up @@ -242,18 +273,57 @@ export class TiptapCollabProvider extends HocuspocusProvider {
}

/**
* Delete a specific thread and all its comments
* Handle the deletion of a thread. By default, the thread and it's comments are not deleted, but marked as deleted
* via the `deletedAt` property. Forceful deletion can be enabled by setting the `force` option to `true`.
*
* If you only want to delete the comments of a thread, you can set the `deleteComments` option to `true`.
* @param id The thread id
* @returns void
* @param options A set of options that control how the thread is deleted
* @returns The deleted thread or null if the thread is not found
*/
deleteThread(id: TCollabThread['id']) {
deleteThread(id: TCollabThread['id'], options?: DeleteThreadOptions) {
const { deleteComments, force } = { ...defaultDeleteThreadOptions, ...options }

const index = this.getThreadIndex(id)

if (index === null) {
return null
}

if (force) {
this.getYThreads().delete(index, 1)
return
}

this.getYThreads().delete(index, 1)
const thread = this.getYThreads().get(index)

thread.set('deletedAt', (new Date()).toISOString())

if (deleteComments) {
thread.set('comments', new Y.Array())
thread.set('deletedComments', new Y.Array())
}

return thread.toJSON() as TCollabThread
}

/**
* Tries to restore a deleted thread
* @param id The thread id
* @returns The restored thread or null if the thread is not found
*/
restoreThread(id: TCollabThread['id']) {
const index = this.getThreadIndex(id)

if (index === null) {
return null
}

const thread = this.getYThreads().get(index)

thread.set('deletedAt', null)

return thread.toJSON() as TCollabThread
}

/**
Expand Down
32 changes: 32 additions & 0 deletions packages/provider/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ export type TCollabThread<Data = any, CommentData = any> = {
id: string;
createdAt: number;
updatedAt: number;
deletedAt: number | null;
resolvedAt?: string; // (new Date()).toISOString()
comments: TCollabComment<CommentData>[];
deletedComments: TCollabComment<CommentData>[];
Expand Down Expand Up @@ -197,3 +198,34 @@ export type DeleteCommentOptions = {
*/
deleteContent?: boolean
}

export type DeleteThreadOptions = {
/**
* If `true`, will remove the comments on the thread,
* otherwise will only mark the thread as deleted
* and keep the comments
* @default false
*/
deleteComments?: boolean

/**
* If `true`, will forcefully remove the thread and all comments,
* otherwise will only mark the thread as deleted
* and keep the comments
* @default false
*/
force?: boolean,
}

/**
* The type of thread
*/
export type ThreadType = 'archived' | 'unarchived'

export type GetThreadsOptions = {
/**
* The types of threads to get
* @default ['unarchived']
*/
types?: Array<ThreadType>
}

0 comments on commit 6a71700

Please sign in to comment.