javascriptpostpugcodemirrorcrdt

How to initialize value of CodeMirror binding to yjs?


My main problem is initializing the text/value of a code editor(CodeMirror) on my website without it affecting the way I save/send POST requests to my backend. The following is the pug code I use for the POST request:

p
  form(id='form' method='POST', action='/docs/edit/'+docs._id)
    textarea(name="doo" id="content" style="display: none;")=docs.content
    textarea(name="foo" id="editortext" style="display: none;")
    input.btn.btn-primary(type='submit' value='Save Doc')

What I'm trying to do here, is send docs.content to textarea with id "content" so that I can use that to initialize the value of my code editor and then put the content of whats in the code editor in the textarea "editortext" once I click the submit button. Thus, the POST request would fetch me the data from both textareas, where I can then save the content of the "editortext" textarea to my database. The logic of the code editor is referenced in the same pug file to a javascript file after rollup transpilation. The following is a chunk of the pre-compiled code:

/* eslint-env browser */

import * as Y from 'yjs'
import { WebsocketProvider } from 'y-websocket'
import { CodeMirrorBinding } from 'y-codemirror'
import CodeMirror from 'codemirror'
import 'codemirror/mode/clike/clike.js'

window.addEventListener('load', () => {
  const ydoc = new Y.Doc()
  const provider = new WebsocketProvider(
    `${location.protocol === 'http:' ? 'ws:' : 'wss:'}${location.host}`,
    'codemirror',
    ydoc
  )
  const yText = ydoc.getText('codemirror')
  const editorContainer = document.createElement('div')
  editorContainer.setAttribute('id', 'editor')
  document.body.insertBefore(editorContainer, null)
  let content = document.getElementById("content").value
  const editor = CodeMirror(editorContainer, {
    mode: 'text/x-java',
    lineNumbers: true
  })

  editor.setValue(content)

  document.getElementById("form").onsubmit = function(evt){
    document.getElementById("editortext").value = editor.getValue();
  }

Most of this code is from the yjs-codemirror demo except for the declaration of the content variable,the invocation of the setValue method, and the document.getElementById("form") block. What this code currently does is send me the right information to my database. However, I am having trouble initializing the value of the code editor when I open up the document. The setValue method doesn't work, neither does doing the following:

  const editor = CodeMirror(editorContainer, {
    value: content,
    mode: 'text/x-java',
    lineNumbers: true
  })

All of the prior examples fail even if I replace the content variable with some string. The only thing that seems to work is the following:

  const editor = CodeMirror(editorContainer, {
    mode: 'text/x-java',
    lineNumbers: true
  }).setValue(content)

However, the problem with this is that for some reason, I get the following errors in the console browser:

TypeError: codeMirror is undefined (y-codemirror.js:160:4)
TypeError: editor is undefined (index.js:28:10)

For reference, the javascript that I have been showing in this question was all from the index.js file. In any case, because the editor is undefined, I can no longer set the value of my "editortext" textarea to the CodeMirror Textarea and I can't save what is written to the code editor to my database. I'm not sure as to why this would happen, I'm not sure if this is particular to the CodeMirrorBinding from yjs but any help on this would be massively appreciated.


Solution

  • The following is quoted from dmonad who is one of the developers of Yjs. For future reference regarding any technical questions about Yjs, you will probably get better luck asking here as there isn't a tag for Yjs yet on StackOverflow.

    Hi @notemaster,

    I assume that you mean you are unable to set the value of the CodeMirror editor.

    The CodeMirrorBinding binds the value of the Y.Text type to a CodeMirror instance. The setValue method works, but the value of the editor is overwritten by the binding:

    ytext.insert(0, 'ytext')
    const editor = CodeMirror(..)
    editor.setValue('my value')
    editor.value() // => "my value"
    new CodeMirrorBinding(editor, ytext)
    editor.value() // => "ytext value"
    

    I suggest that you set the value after it has been bound to the YText type.

    Another note: There is nothing like a default value in Yjs. Initially, the Yjs document is empty until it synced with the server. So you might want to wait until the client synced with the server before setting the value.

    const setDefaultVal = () => {
      if (ytext.toString() === '') {
        ytext.insert(0, 'my default value')
      }
    }
    if (provider.synced) {
      setDefaultVal()
    } else {
      provider.once('synced', setDefaultVal)
    }
    
     const editor = CodeMirror(editorContainer, {
      mode: 'text/x-java',
      lineNumbers: true
    }).setValue(content)
    

    I assume editor.setValue() returns undefined . This is why the binding won’t work and you can set the initial value of the editor.