Hi I’m creating a collab editor for my final year project. I have a node.js express backend with socket.io for comunication and a Vue.js frontend.
I have the editor setup with the collab plugin and I can send updates to the server and I get the steps back but I’m unable to update the state of the editor.
I tried with
this.view.dispatch(collab.receiveTransaction(this.state, data.steps, data.clientIDs))
but I get an Uncaught RangeError: Applying a mismatched transaction
I’m a bit lost but I think I’m almost there. ¿Any ideas of what I’m missing?
Thanks in advance.
Thats the (simplified) code for the server
// Collab editor (server.js)
const { schema } = require('prosemirror-schema-basic')
const { Step } = require('prosemirror-transform')
const defaultData = {
version: 0,
doc: {
type: 'doc',
content: [
{
type: 'paragraph',
content: [{ type: 'text', text: "Let's start collaborating. Yeah!" }]
}
]
}
}
const io = require('socket.io')(server)
function getDoc() {
return defaultData
}
io.on('connection', (socket) => {
socket.emit('init', getDoc())
socket.on('update', ({ version, steps, clientID }) => {
const storedData = getDoc()
let doc = schema.nodeFromJSON(storedData.doc)
let newSteps = steps.map((step) => {
const newStep = Step.fromJSON(schema, step)
newStep.clientID = clientID
// apply step to document
let result = newStep.apply(doc)
doc = result.doc
return newStep
})
let clientIDs = newSteps.map((step) => step.clientID)
const newVersion = version + newSteps.length
io.sockets.emit('update', {
version: newVersion,
steps: newSteps,
clientIDs
})
})
socket.on('disconnect', () => {
console.log('user disconnected')
})
})
and here is the Vue code
<template>
<div>
<div v-if="initialContentVisible" ref="initialContent">
Let's start collaborating. Yeah!
</div>
<div style="border: 1px solid black" ref="editor"></div>
</div>
</template>
<script>
import { EditorState } from 'prosemirror-state'
import { EditorView } from 'prosemirror-view'
import { DOMParser } from 'prosemirror-model'
import { schema } from 'prosemirror-schema-basic'
import { keymap } from 'prosemirror-keymap'
import { undo, redo, history } from 'prosemirror-history'
const collab = require('prosemirror-collab')
import io from 'socket.io-client'
import {
// toggleMark,
chainCommands,
newlineInCode,
createParagraphNear,
liftEmptyBlock,
splitBlockKeepMarks,
baseKeymap
} from 'prosemirror-commands'
export default {
components: {},
props: {},
data() {
return {
initialContentVisible: true,
state: null,
view: null,
socket: io('http://localhost:5000')
}
},
methods: {
onInit({ doc, version }) {
console.log('ON_INIT', doc, version)
this.state = EditorState.create({
doc: DOMParser.fromSchema(schema).parse(doc),
plugins: [
collab.collab({ version }),
history(),
keymap(baseKeymap),
keymap({
'Mod-z': undo,
'Mod-y': redo,
Enter: chainCommands(
newlineInCode,
createParagraphNear,
liftEmptyBlock,
splitBlockKeepMarks
)
})
]
})
let self = this
this.view = new EditorView(this.$refs.editor, {
state: this.state,
dispatchTransaction(transaction) {
let newState = self.view.state.apply(transaction)
self.view.updateState(newState)
let sendable = collab.sendableSteps(newState)
if (sendable) {
self.socket.emit('update', sendable)
}
}
})
this.initialContentVisible = false
},
onUpdate(data) {
this.view.dispatch(
collab.receiveTransaction(this.state, data.steps, data.clientIDs)
)
}
},
mounted() {
this.socket.on('init', (data) => this.onInit(data))
this.socket.on('update', (data) => this.onUpdate(data))
}
}
</script>