Skip to content

Commit

Permalink
Feature/threads (#778)
Browse files Browse the repository at this point in the history
* temporary: comments implementation for provider

* maps

* yjs implementation

* threads sync

* threads sync

---------

Co-authored-by: bdbch <dominik@bdbch.com>
Co-authored-by: Dominik Biedebach <dominik.biedebach@ueber.io>
  • Loading branch information
3 people authored Jan 22, 2024
1 parent af7ecf6 commit 9093e3c
Show file tree
Hide file tree
Showing 5 changed files with 208 additions and 1 deletion.
3 changes: 3 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"editor.defaultFormatter": "dbaeumer.vscode-eslint"
}
19 changes: 19 additions & 0 deletions package-lock.json

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

1 change: 1 addition & 0 deletions packages/provider/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
"@hocuspocus/common": "^2.9.1-rc.0",
"@lifeomic/attempt": "^3.0.2",
"lib0": "^0.2.87",
"uuid": "^9.0.0",
"ws": "^8.14.2"
},
"peerDependencies": {
Expand Down
170 changes: 169 additions & 1 deletion packages/provider/src/TiptapCollabProvider.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
import type { AbstractType, YArrayEvent } from 'yjs'
import * as Y from 'yjs'
import { uuidv4 } from 'lib0/random'
import {
HocuspocusProvider,
HocuspocusProviderConfiguration,
} from './HocuspocusProvider.js'

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

export type TiptapCollabProviderConfiguration =
Required<Pick<HocuspocusProviderConfiguration, 'name'>> &
Expand Down Expand Up @@ -93,4 +97,168 @@ export class TiptapCollabProvider extends HocuspocusProvider {
return this.configuration.document.getMap<number>(`${this.tiptapCollabConfigurationPrefix}config`).set('autoVersioning', 0)
}

private getYThreads() {
return this.configuration.document.getArray<Y.Map<any>>(`${this.tiptapCollabConfigurationPrefix}threads`)
}

getThreads<Data, CommentData>(): TCollabThread<Data, CommentData>[] {
return this.getYThreads().toJSON() as TCollabThread<Data, CommentData>[]
}

private getThreadIndex(id: string): number | null {
let index = null

let i = 0
// eslint-disable-next-line no-restricted-syntax
for (const thread of this.getThreads()) {
if (thread.id === id) {
index = i
break
}
i += 1
}

return index
}

getThread<Data, CommentData>(id: string): TCollabThread<Data, CommentData> | null {
const index = this.getThreadIndex(id)

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

return this.getYThreads().get(index).toJSON() as TCollabThread<Data, CommentData>
}

private getYThread(id: string) {
const index = this.getThreadIndex(id)

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

return this.getYThreads().get(index)
}

createThread(data: TCollabThread) {
const thread = new Y.Map()
thread.set('id', uuidv4())
thread.set('createdAt', (new Date()).toISOString())
thread.set('comments', new Y.Array())

this.getYThreads().push([thread])
return this.updateThread(String(thread.get('id')), data)
}

updateThread(id: TCollabThread['id'], data: Pick<TCollabThread, 'data'>) {
const thread = this.getYThread(id)

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

thread.set('updatedAt', (new Date()).toISOString())
thread.set('data', data.data)

return thread.toJSON() as TCollabThread
}

deleteThread(id: TCollabThread['id']) {
const index = this.getThreadIndex(id)

if (index === null) {
return
}

this.getYThreads().delete(index, 1)
}

getThreadComments(threadId: TCollabThread['id']): TCollabComment[] | null {
const index = this.getThreadIndex(threadId)

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

return this.getThread(threadId)?.comments ?? []
}

getThreadComment(threadId: TCollabThread['id'], commentId: TCollabComment['id']): TCollabComment | null {
const index = this.getThreadIndex(threadId)

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

return this.getThread(threadId)?.comments.find(comment => comment.id === commentId) ?? null
}

addComment(threadId: TCollabThread['id'], data: TCollabComment) {
const thread = this.getYThread(threadId)

if (thread === null) return null

const commentMap = new Y.Map()
commentMap.set('id', uuidv4())
commentMap.set('createdAt', (new Date()).toISOString())
thread.get('comments').push([commentMap])

this.updateComment(threadId, String(commentMap.get('id')), data)

return thread.toJSON() as TCollabThread
}

updateComment(threadId: TCollabThread['id'], commentId: TCollabComment['id'], data: TCollabComment) {
const thread = this.getYThread(threadId)

if (thread === null) return null

let comment = null
// eslint-disable-next-line no-restricted-syntax
for (const c of thread.get('comments')) {
if (c.get('id') === commentId) {
comment = c
break
}
}

if (comment === null) return null

comment.set('updatedAt', (new Date()).toISOString())
comment.set('data', data.data)
comment.set('content', data.content)

return thread.toJSON() as TCollabThread
}

deleteComment(threadId: TCollabThread['id'], commentId: TCollabComment['id']) {
const thread = this.getYThread(threadId)

if (thread === null) return null

let commentIndex = 0
// eslint-disable-next-line no-restricted-syntax
for (const c of thread.get('comments')) {
if (c.get('id') === commentId) {
break
}
commentIndex += 1
}

if (commentIndex >= 0) {
thread.get('comments').delete(commentIndex)
}

return thread.toJSON() as TCollabThread
}

watchThreads(callback: () => void) {
this.getYThreads().observeDeep(callback)
}

unwatchThreads(callback: () => void) {
this.getYThreads().unobserveDeep(callback)
}

}
16 changes: 16 additions & 0 deletions packages/provider/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,22 @@ export type StatesArray = { clientId: number, [key: string | number]: any }[]

// hocuspocus-pro types

export type TCollabThread<Data = any, CommentData = any> = {
id: string;
createdAt: number;
updatedAt: number;
comments: TCollabComment<CommentData>[];
data: Data
}

export type TCollabComment<Data = any> = {
id: string;
createdAt: number;
updatedAt: number;
data: Data
content: any
}

export type THistoryVersion = {
name?: string;
version: number;
Expand Down

0 comments on commit 9093e3c

Please sign in to comment.