Skip to content

Collaboration

Below is an example of a basic plain text editor using lexical, lexical-vue, and yjs

Tip: you can easily run a local y-websocket server by running:

bash
HOST=localhost PORT=1234 npx y-websocket

Note: You shouldn't use LexicalHistoryPlugin with LexicalCollaborationPlugin because LexicalCollaborationPlugin already has history built in.

vue
<script setup lang="ts">
import type { LexicalEditor } from 'lexical'
import { $createParagraphNode, $createTextNode, $getRoot } from 'lexical'
import { LexicalComposer } from 'lexical-vue/LexicalComposer'
import { ContentEditable } from 'lexical-vue/LexicalContentEditable'
import { RichTextPlugin } from 'lexical-vue/LexicalRichTextPlugin'
import { AutoFocusPlugin } from 'lexical-vue/LexicalAutoFocusPlugin'
import { CollaborationPlugin } from 'lexical-vue/LexicalCollaborationPlugin'
import { ref } from 'vue'
import { WebsocketProvider } from 'y-websocket'
import * as Y from 'yjs'

const cursorsContainerRef = ref<HTMLElement | null>(null)

// Optional initial editor state in case collaborative Y.Doc won't
// have any existing data on server. Then it'll user this value to populate editor.
// It accepts same type of values as LexicalComposer editorState
// prop (json string, state object, or a function)
function initialEditorState(editor: LexicalEditor): void {
  const root = $getRoot()
  const paragraph = $createParagraphNode()
  const text = $createTextNode('Welcome to collab!')
  paragraph.append(text)
  root.append(paragraph)
}

const config = {
  // NOTE: This is critical for collaboration plugin to set editor state to null. It
  // would indicate that the editor should not try to set any default state
  // (not even empty one), and let collaboration plugin do it instead
  editorState: null as any,
  namespace: 'Demo',
  nodes: [],
  editable: true,
  theme: {},
  onError,
}

function providerFactory(id: string, yjsDocMap: Map<string, Y.Doc>) {
  const doc = new Y.Doc()
  yjsDocMap.set(id, doc)

  const provider = new WebsocketProvider('ws://localhost:1234', id, doc)

  return provider as any
}

function onError(error: Error) {
  throw error
}
</script>

<template>
  <LexicalComposer :initial-config="config">
    <div ref="cursorsContainerRef" class="editor-container">
      <div class="editor-inner">
        <RichTextPlugin>
          <template #contentEditable>
            <ContentEditable class="editor-input" />
          </template>
          <template #placeholder>
            <div class="editor-placeholder">Enter some collaboration text...</div>
          </template>
        </RichTextPlugin>
        <AutoFocusPlugin />
        <CollaborationPlugin
          id="yjs-plugin"
          :provider-factory="providerFactory"
          :initial-editor-state="initialEditorState"
          :should-bootstrap="true"
          :cursors-container-ref="cursorsContainerRef"
        />
      </div>
    </div>
  </LexicalComposer>
</template>

Released under the MIT License.