"use strict"; import type { LexicalEditor, LexicalNode } from "Lexical"; import type { Binding, Provider } from "LexicalYjs"; import { createBinding, syncLexicalUpdateToYjs, syncYjsChangesToLexical, } from "LexicalYjs"; import { Doc, encodeStateAsUpdate } from "yjs"; export default function headlessConvertYDocStateToLexicalJSON( nodes: Array>, yDocState: Uint8Array ): SerializedEditorState { const { binding, editor } = createHeadlessCollaborationEditor(nodes); applyUpdate(binding.doc, yDocState, { isUpdateRemote: true }); editor.update(() => {}, { discrete: true }); return editor.getEditorState().toJSON(); } /** * Creates headless collaboration editor with no-op provider (since it won't * connect to message distribution infra) and binding. It also sets up * bi-directional synchronization between yDoc and editor */ function createHeadlessCollaborationEditor(nodes: Array>): { editor: LexicalEditor, provider: Provider, binding: Binding, } { const editor = createEditor({ headless: true, namespace: "headless", nodes, onError: (error) => { throw error; }, }); const id = "main"; const doc = new Doc(); const docMap = new Map([[id, doc]]); const provider = createNoOpProvider(); const binding = createBinding(editor, provider, id, doc, docMap); registerCollaborationListeners(editor, provider, binding); return { binding, editor, provider, }; } function registerCollaborationListeners( editor: LexicalEditor, provider: Provider, binding: Binding ): void { editor.registerUpdateListener( ({ dirtyElements, dirtyLeaves, editorState, normalizedNodes, prevEditorState, tags, }) => { if (tags.has("skip-collab") === false) { syncLexicalUpdateToYjs( binding, provider, prevEditorState, editorState, dirtyElements, dirtyLeaves, normalizedNodes, tags ); } } ); binding.root.getSharedType().observeDeep((events, transaction) => { if (transaction?.origin !== binding) { syncYjsChangesToLexical(binding, provider, events); } }); } function createNoOpProvider(): Provider { const emptyFunction = () => {}; return { awareness: { getLocalState: () => null, getStates: () => new Map(), off: emptyFunction, on: emptyFunction, setLocalState: emptyFunction, }, connect: emptyFunction, disconnect: emptyFunction, off: emptyFunction, on: emptyFunction, }; }