UNPKG

@blocknote/core

Version:

A "Notion-style" block-based extensible text editor built on top of Prosemirror and Tiptap.

109 lines (95 loc) 3.52 kB
import * as Y from "@y/y"; import { createExtension, createStore, ExtensionOptions, } from "../../editor/BlockNoteExtension.js"; import { CollaborationOptions } from "./index.js"; import { YCursorExtension } from "./YCursorPlugin.js"; import { findTypeInOtherYdoc } from "../utils.js"; import { configureYProsemirror } from "@y/prosemirror"; export const ForkYDocExtension = createExtension( ({ editor, options }: ExtensionOptions<CollaborationOptions>) => { let forkedState: | { originalFragment: Y.Type; forkedFragment: Y.Type; } | undefined = undefined; const store = createStore({ isForked: false }); return { key: "yForkDoc", store, /** * Fork the Y.js document from syncing to the remote, * allowing modifications to the document without affecting the remote. * These changes can later be rolled back or applied to the remote. */ fork({ /** * The initial update to apply to the forked document. */ initialUpdate, }: { initialUpdate?: Uint8Array; } = {}) { if (forkedState) { return; } const originalFragment = options.fragment; if (!originalFragment) { throw new Error("No fragment to fork from"); } const doc = new Y.Doc(); // Copy the original document to a new Yjs document Y.applyUpdateV2( doc, initialUpdate ?? Y.encodeStateAsUpdateV2(originalFragment.doc!), ); // Find the forked fragment in the new Yjs document const forkedFragment = findTypeInOtherYdoc(originalFragment, doc); forkedState = { originalFragment, forkedFragment, }; // Need to reset all the yjs plugins editor.unregisterExtension([YCursorExtension]); editor.exec(configureYProsemirror({ ytype: forkedFragment })); // Tell the store that the editor is now forked store.setState({ isForked: true }); }, /** * Resume syncing the Y.js document to the remote * If `keepChanges` is true, any changes that have been made to the forked document will be applied to the original document. * Otherwise, the original document will be restored and the changes will be discarded. */ merge({ keepChanges }: { keepChanges: boolean }) { if (!forkedState) { return; } const { originalFragment, forkedFragment } = forkedState; // Register the plugins again, based on the original fragment (which is still in the original options) editor.registerExtension([YCursorExtension(options)]); editor.exec( configureYProsemirror({ ytype: originalFragment, renderer: options.renderer, }), ); if (keepChanges) { // Apply any changes that have been made to the fork, onto the original doc const update = Y.encodeStateAsUpdate( forkedFragment.doc!, Y.encodeStateVector(originalFragment.doc!), ); // Applying this change will add to the undo stack, allowing it to be undone normally Y.applyUpdate(originalFragment.doc!, update, editor); } // Reset the forked state forkedState = undefined; // Tell the store that the editor is no longer forked store.setState({ isForked: false }); }, } as const; }, );