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, { type Text as YorkieText, OperationInfo } from 'yorkie-js-sdk';3import { basicSetup, EditorView } from 'codemirror';4import { keymap } from '@codemirror/view';5import {6 markdown,7 markdownKeymap,8 markdownLanguage,9} from '@codemirror/lang-markdown';10import { Transaction } from '@codemirror/state';11import { network } from './network';12import { displayLog, displayPeers } from './utils';13import './style.css';1415type YorkieDoc = {16 content: YorkieText;17};1819const editorParentElem = document.getElementById('editor')!;20const peersElem = document.getElementById('peers')!;21const documentElem = document.getElementById('document')!;22const documentTextElem = document.getElementById('document-text')!;23const networkStatusElem = document.getElementById('network-status')!;2425async function main() {26 // 01. create client with RPCAddr(envoy) then activate it.27 const client = new yorkie.Client(import.meta.env.VITE_YORKIE_API_ADDR, {28 apiKey: import.meta.env.VITE_YORKIE_API_KEY,29 });30 await client.activate();3132 // subscribe peer change event33 client.subscribe((event) => {34 network.statusListener(networkStatusElem)(event);35 if (event.type === 'peers-changed') {36 displayPeers(37 peersElem,38 client.getPeersByDocKey(doc.getKey()),39 client.getID()!,40 );41 }42 });4344 // 02-1. create a document then attach it into the client.45 const doc = new yorkie.Document<YorkieDoc>(46 `codemirror6-${new Date()47 .toISOString()48 .substring(0, 10)49 .replace(/-/g, '')}`,50 );51 await client.attach(doc);52 doc.update((root) => {53 if (!root.content) {54 root.content = new yorkie.Text();55 }56 }, 'create content if not exists');5758 // 02-2. subscribe document event.59 const syncText = () => {60 const text = doc.getRoot().content;61 view.dispatch({62 changes: { from: 0, to: view.state.doc.length, insert: text.toString() },63 annotations: [Transaction.remote.of(true)],64 });65 };66 doc.subscribe((event) => {67 if (event.type === 'snapshot') {68 // The text is replaced to snapshot and must be re-synced.69 syncText();70 }71 displayLog(documentElem, documentTextElem, doc);72 });7374 doc.subscribe('$.content', (event) => {75 if (event.type === 'remote-change') {76 const { operations } = event.value;77 handleOperations(operations);78 }79 });8081 await client.sync();8283 // 03-1. define function that bind the document with the codemirror(broadcast local changes to peers)84 const updateListener = EditorView.updateListener.of((viewUpdate) => {85 if (viewUpdate.docChanged) {86 for (const tr of viewUpdate.transactions) {87 const events = ['select', 'input', 'delete', 'move', 'undo', 'redo'];88 if (!events.map((event) => tr.isUserEvent(event)).some(Boolean)) {89 continue;90 }91 if (tr.annotation(Transaction.remote)) {92 continue;93 }94 tr.changes.iterChanges((fromA, toA, _, __, inserted) => {95 doc.update((root) => {96 root.content.edit(fromA, toA, inserted.toJSON().join('\n'));97 }, `update content byA ${client.getID()}`);98 });99 }100 }101 });102103 // 03-2. create codemirror instance104 const view = new EditorView({105 doc: '',106 extensions: [107 basicSetup,108 markdown({ base: markdownLanguage }),109 keymap.of(markdownKeymap),110 updateListener,111 ],112 parent: editorParentElem,113 });114115 // 03-3. define event handler that apply remote changes to local116 function handleOperations(operations: Array<OperationInfo>) {117 operations.forEach((op) => {118 if (op.type === 'edit') {119 handleEditOp(op);120 }121 });122 }123 function handleEditOp(op: any) {124 const changes = [125 {126 from: Math.max(0, op.from),127 to: Math.max(0, op.to),128 insert: op.value!.content,129 },130 ];131132 view.dispatch({133 changes,134 annotations: [Transaction.remote.of(true)],135 });136 }137138 syncText();139 displayLog(documentElem, documentTextElem, doc);140}141142main();
- User 1
- User 2
- User 1
- User 2
Event Log