CodeMirror
This is a real-time collaborative example of the CodeMirror 6 editor. It uses the Text, a custom CRDT type from Yorkie.
main.ts
1/* eslint-disable jsdoc/require-jsdoc */2import yorkie, {3 type TextChange,4 type Text as YorkieText,5} from 'yorkie-js-sdk';6import { basicSetup, EditorView } from 'codemirror';7import { keymap } from '@codemirror/view';8import {9 markdown,10 markdownKeymap,11 markdownLanguage,12} from '@codemirror/lang-markdown';13import { Transaction, type ChangeSpec } from '@codemirror/state';14import { network } from './network';15import { displayLog, displayPeers } from './utils';16import './style.css';1718type YorkieDoc = {19 content: YorkieText;20};2122const editorParentElem = document.getElementById('editor')!;23const peersElem = document.getElementById('peers')!;24const documentElem = document.getElementById('document')!;25const documentTextElem = document.getElementById('document-text')!;26const networkStatusElem = document.getElementById('network-status')!;2728async function main() {29 // 01. create client with RPCAddr(envoy) then activate it.30 const client = new yorkie.Client(import.meta.env.VITE_YORKIE_API_ADDR, {31 apiKey: import.meta.env.VITE_YORKIE_API_KEY,32 });33 await client.activate();3435 // subscribe peer change event36 client.subscribe((event) => {37 network.statusListener(networkStatusElem)(event);38 if (event.type === 'peers-changed') {39 displayPeers(40 peersElem,41 client.getPeersByDocKey(doc.getKey()),42 client.getID()!,43 );44 }45 });4647 // 02-1. create a document then attach it into the client.48 const doc = new yorkie.Document<YorkieDoc>(49 `codemirror6-${new Date()50 .toISOString()51 .substring(0, 10)52 .replace(/-/g, '')}`,53 );54 await client.attach(doc);55 doc.update((root) => {56 if (!root.content) {57 root.content = new yorkie.Text();58 }59 }, 'create content if not exists');6061 // 02-2. subscribe document event.62 const syncText = () => {63 const text = doc.getRoot().content;64 view.dispatch({65 changes: { from: 0, to: view.state.doc.length, insert: text.toString() },66 annotations: [Transaction.remote.of(true)],67 });68 };69 doc.subscribe((event) => {70 if (event.type === 'snapshot') {71 // The text is replaced to snapshot and must be re-synced.72 syncText();73 }74 displayLog(documentElem, documentTextElem, doc);75 });76 await client.sync();7778 // 03-1. define function that bind the document with the codemirror(broadcast local changes to peers)79 const updateListener = EditorView.updateListener.of((viewUpdate) => {80 if (viewUpdate.docChanged) {81 for (const tr of viewUpdate.transactions) {82 const events = ['select', 'input', 'delete', 'move', 'undo', 'redo'];83 if (!events.map((event) => tr.isUserEvent(event)).some(Boolean)) {84 continue;85 }86 if (tr.annotation(Transaction.remote)) {87 continue;88 }89 tr.changes.iterChanges((fromA, toA, _, __, inserted) => {90 doc.update((root) => {91 root.content.edit(fromA, toA, inserted.toJSON().join('\n'));92 }, `update content byA ${client.getID()}`);93 });94 }95 }96 });9798 // 03-2. create codemirror instance99 const view = new EditorView({100 doc: '',101 extensions: [102 basicSetup,103 markdown({ base: markdownLanguage }),104 keymap.of(markdownKeymap),105 updateListener,106 ],107 parent: editorParentElem,108 });109110 // 03-3. define event handler that apply remote changes to local111 const changeEventHandler = (changes: Array<TextChange>) => {112 const clientId = client.getID();113 const changeSpecs: Array<ChangeSpec> = changes114 .filter(115 (change) => change.type === 'content' && change.actor !== clientId,116 )117 .map((change) => ({118 from: Math.max(0, change.from),119 to: Math.max(0, change.to),120 insert: change.value!.content,121 }));122123 view.dispatch({124 changes: changeSpecs,125 annotations: [Transaction.remote.of(true)],126 });127 };128 const text = doc.getRoot().content;129 text.onChanges(changeEventHandler);130 syncText();131 displayLog(documentElem, documentTextElem, doc);132}133134main();
- User 1
- User 2
- User 1
- User 2
Event Log