Collab with socket.io and Vue. How to apply changes back to editor view?

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>

Looks like this.state and view.state aren’t the same state. You can only dispatch transactions that start from view.state on a given view.

Thank you. I will look into it