ProseMirror
This is a real-time collaborative example of the ProseMirror editor. It uses the Tree, a custom CRDT type from Yorkie with the @yorkie-js/prosemirror binding.
main.ts
1import yorkie from '@yorkie-js/sdk';2import { EditorState } from 'prosemirror-state';3import { EditorView } from 'prosemirror-view';4import { Schema } from 'prosemirror-model';5import { schema as basicSchema } from 'prosemirror-schema-basic';6import { addListNodes } from 'prosemirror-schema-list';7import { exampleSetup } from 'prosemirror-example-setup';8import {9 YorkieProseMirrorBinding,10 remoteSelectionPlugin,11} from '@yorkie-js/prosemirror';12import './style.css';1314const statusEl = document.getElementById('status')!;15const editorEl = document.getElementById('editor')!;16const cursorOverlayEl = document.getElementById('cursor-overlay')!;17const editorWrapperEl = document.getElementById('editor-wrapper')!;1819// Extend basic schema with list nodes20const mySchema = new Schema({21 nodes: addListNodes(basicSchema.spec.nodes, 'paragraph block*', 'block'),22 marks: basicSchema.spec.marks,23});2425// Document key from URL query param or date-based fallback26const params = new URLSearchParams(window.location.search);27const docKey =28 params.get('key') ||29 `pm-vanilla-${new Date().toISOString().substring(0, 10).replace(/-/g, '')}`;3031function setStatus(text: string, type: 'connecting' | 'connected' | 'error') {32 statusEl.textContent = text;33 statusEl.className = `status ${type === 'connecting' ? '' : type}`;34}3536// Initial document37const initialDoc = mySchema.node('doc', null, [38 mySchema.node('heading', { level: 2 }, [39 mySchema.text('Collaborative ProseMirror'),40 ]),41 mySchema.node('paragraph', null, [42 mySchema.text('Start editing to collaborate in real time.'),43 ]),44]);4546async function main() {47 setStatus(`Connecting to Yorkie server... (doc: ${docKey})`, 'connecting');4849 try {50 const client = new yorkie.Client({51 rpcAddr:52 (import.meta as any).env?.VITE_YORKIE_API_ADDR ||53 'http://localhost:8080',54 apiKey: (import.meta as any).env?.VITE_YORKIE_API_KEY,55 });56 await client.activate();5758 const doc = new yorkie.Document<Record<string, any>, Record<string, any>>(59 docKey,60 { enableDevtools: true },61 );62 await client.attach(doc, { initialPresence: {} });6364 setStatus(`Connected — doc: ${docKey}`, 'connected');6566 const state = EditorState.create({67 doc: initialDoc,68 plugins: [69 ...exampleSetup({ schema: mySchema }),70 remoteSelectionPlugin(),71 ],72 });7374 const view = new EditorView(editorEl, { state });7576 const binding = new YorkieProseMirrorBinding(view, doc, 'tree', {77 cursors: {78 enabled: true,79 overlayElement: cursorOverlayEl,80 wrapperElement: editorWrapperEl,81 },82 });83 binding.initialize();8485 view.focus();86 } catch (e) {87 setStatus(`Connection failed: ${(e as Error).message}`, 'error');88 console.error(e);89 }90}9192main();
- User 1
- User 2
- User 1
- User 2