UBER-853 Move snapshot creation to collaboration provider (#3807)

Signed-off-by: Alexander Onnikov <alexander.onnikov@xored.com>
This commit is contained in:
Alexander Onnikov 2023-10-07 12:17:06 +07:00 committed by GitHub
parent c8d569621d
commit ca0dd235b6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 91 additions and 18 deletions

View File

@ -1,6 +1,6 @@
<!-- <!--
// //
// Copyright © 2022 Hardcore Engineering Inc. // Copyright © 2022, 2023 Hardcore Engineering Inc.
// //
// Licensed under the Eclipse Public License, Version 2.0 (the "License"); // Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may // you may not use this file except in compliance with the License. You may
@ -16,9 +16,11 @@
--> -->
<script lang="ts"> <script lang="ts">
import { onDestroy, setContext } from 'svelte' import { onDestroy, setContext } from 'svelte'
import { WebsocketProvider } from 'y-websocket'
import * as Y from 'yjs' import * as Y from 'yjs'
import { TiptapCollabProvider } from '../provider'
import { CollaborationIds } from '../types' import { CollaborationIds } from '../types'
export let documentId: string export let documentId: string
export let token: string export let token: string
export let collaboratorURL: string export let collaboratorURL: string
@ -27,33 +29,35 @@
let _documentId = '' let _documentId = ''
let wsProvider: WebsocketProvider | undefined let provider: TiptapCollabProvider | undefined
$: if (_documentId !== documentId) { $: if (_documentId !== documentId) {
_documentId = documentId _documentId = documentId
if (wsProvider !== undefined) { if (provider !== undefined) {
wsProvider.disconnect() provider.disconnect()
} }
const ydoc: Y.Doc = new Y.Doc() const ydoc: Y.Doc = new Y.Doc()
wsProvider = new WebsocketProvider(collaboratorURL, documentId, ydoc, { provider = new TiptapCollabProvider({
params: { url: collaboratorURL,
token, name: documentId,
documentId, document: ydoc,
token,
parameters: {
initialContentId: initialContentId ?? '' initialContentId: initialContentId ?? ''
} }
}) })
setContext(CollaborationIds.Doc, ydoc) setContext(CollaborationIds.Doc, ydoc)
setContext(CollaborationIds.Provider, wsProvider) setContext(CollaborationIds.Provider, provider)
wsProvider.on('status', (event: any) => { provider.on('status', (event: any) => {
console.log('Collaboration:', documentId, event.status) // logs "connected" or "disconnected" console.log('Collaboration:', documentId, event.status) // logs "connected" or "disconnected"
}) })
wsProvider.on('synched', (event: any) => { provider.on('synced', (event: any) => {
console.log('Collaboration:', event) // logs "connected" or "disconnected" console.log('Collaboration:', event) // logs "connected" or "disconnected"
}) })
} }
onDestroy(() => { onDestroy(() => {
wsProvider?.disconnect() provider?.destroy()
}) })
</script> </script>

View File

@ -19,7 +19,6 @@
import { DecorationSet } from 'prosemirror-view' import { DecorationSet } from 'prosemirror-view'
import { getContext, createEventDispatcher, onDestroy, onMount } from 'svelte' import { getContext, createEventDispatcher, onDestroy, onMount } from 'svelte'
import * as Y from 'yjs' import * as Y from 'yjs'
import { HocuspocusProvider } from '@hocuspocus/provider'
import { AnyExtension, Editor, Extension, HTMLContent, getMarkRange, mergeAttributes } from '@tiptap/core' import { AnyExtension, Editor, Extension, HTMLContent, getMarkRange, mergeAttributes } from '@tiptap/core'
import Collaboration, { isChangeOrigin } from '@tiptap/extension-collaboration' import Collaboration, { isChangeOrigin } from '@tiptap/extension-collaboration'
import CollaborationCursor from '@tiptap/extension-collaboration-cursor' import CollaborationCursor from '@tiptap/extension-collaboration-cursor'
@ -30,15 +29,16 @@
import { Completion } from '../Completion' import { Completion } from '../Completion'
import textEditorPlugin from '../plugin' import textEditorPlugin from '../plugin'
import { TiptapCollabProvider } from '../provider'
import { CollaborationIds, TextFormatCategory, TextNodeAction } from '../types' import { CollaborationIds, TextFormatCategory, TextNodeAction } from '../types'
import { calculateDecorations } from './diff/decorations' import { calculateDecorations } from './diff/decorations'
import { defaultEditorAttributes } from './editor/editorProps'
import { completionConfig, defaultExtensions } from './extensions' import { completionConfig, defaultExtensions } from './extensions'
import { InlineStyleToolbar } from './extension/inlineStyleToolbar' import { InlineStyleToolbar } from './extension/inlineStyleToolbar'
import { NodeUuidExtension } from './extension/nodeUuid' import { NodeUuidExtension } from './extension/nodeUuid'
import StyleButton from './StyleButton.svelte' import StyleButton from './StyleButton.svelte'
import TextEditorStyleToolbar from './TextEditorStyleToolbar.svelte' import TextEditorStyleToolbar from './TextEditorStyleToolbar.svelte'
import { defaultEditorAttributes } from './editor/editorProps'
export let documentId: string export let documentId: string
export let readonly = false export let readonly = false
@ -66,11 +66,11 @@
const ydoc = (getContext(CollaborationIds.Doc) as Y.Doc | undefined) ?? new Y.Doc() const ydoc = (getContext(CollaborationIds.Doc) as Y.Doc | undefined) ?? new Y.Doc()
const contextProvider = getContext(CollaborationIds.Provider) as HocuspocusProvider | undefined const contextProvider = getContext(CollaborationIds.Provider) as TiptapCollabProvider | undefined
const provider = const provider =
contextProvider ?? contextProvider ??
new HocuspocusProvider({ new TiptapCollabProvider({
url: collaboratorURL, url: collaboratorURL,
name: documentId, name: documentId,
document: ydoc, document: ydoc,
@ -142,6 +142,10 @@
}) })
} }
export function takeSnapshot (snapshotId: string) {
provider.copyContent(documentId, snapshotId)
}
let needFocus = false let needFocus = false
let focused = false let focused = false
@ -274,7 +278,6 @@
editor.destroy() editor.destroy()
} catch (err: any) {} } catch (err: any) {}
if (contextProvider === undefined) { if (contextProvider === undefined) {
provider.configuration.websocketProvider.disconnect()
provider.destroy() provider.destroy()
} }
} }

View File

@ -0,0 +1,38 @@
//
// Copyright © 2023 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
//
import { HocuspocusProvider, HocuspocusProviderConfiguration } from '@hocuspocus/provider'
export type TiptapCollabProviderConfiguration = HocuspocusProviderConfiguration &
Required<Pick<HocuspocusProviderConfiguration, 'token'>>
export class TiptapCollabProvider extends HocuspocusProvider {
constructor (configuration: TiptapCollabProviderConfiguration) {
super(configuration as HocuspocusProviderConfiguration)
}
copyContent (sourceId: string, targetId: string): void {
const payload = {
action: 'document.copy',
params: { sourceId, targetId }
}
this.sendStateless(JSON.stringify(payload))
}
destroy (): void {
this.configuration.websocketProvider.disconnect()
super.destroy()
}
}

View File

@ -38,3 +38,31 @@ export function yDocContentToNode (extensions: Extensions, content: ArrayBuffer,
return schema.node(schema.topNodeType) return schema.node(schema.topNodeType)
} }
} }
/**
* Get ProseMirror nodes from Y.Doc content
*
* @public
*/
export function yDocContentToNodes (extensions: Extensions, content: ArrayBuffer): Node[] {
const schema = getSchema(extensions)
const nodes: Node[] = []
try {
const ydoc = new Doc()
const uint8arr = new Uint8Array(content)
applyUpdate(ydoc, uint8arr)
for (const field of ydoc.share.keys()) {
try {
const body = yDocToProsemirrorJSON(ydoc, field)
nodes.push(schema.nodeFromJSON(body))
} catch {}
}
} catch (err: any) {
console.error(err)
}
return nodes
}