scniro / react-codemirror2

Codemirror integrated components for React
MIT License
1.66k stars 193 forks source link

commands.save #53

Closed ellamental closed 6 years ago

ellamental commented 6 years ago

I'm trying to add a handler for the save command. I came across #46 and tried the suggestion to use editorDidMount, but I can't seem to find the commands attribute.

import {Controlled as CodeMirror} from 'react-codemirror2'

export class TextEditor extends React.Component {
  onEditorDidMount = (editor) => {
    // This is the only place I've been able to find the `save` command.
    editor.__proto__.constructor.commands.save = this.onSave;

    editor.commands === undefined  // ==> true
    editor.save === undefined  // ==> true
  }

  render() {
    // I've searched around the ref a bit too (which seems to be different from the `editor`
    // passed to `onEditorDidMount`, but no luck.
    component = <CodeMirror ... ref={((ref) => { this.cm = ref; })} />
  }

  componentDidMount = () => {
    this.cm.commands === undefined  // ==> true
    this.cm.save === undefined  // ==> true

    this.cm.mirror.commands === undefined  // ==> true
    this.cm.mirror.save === undefined  // ==> true

    this.cm.editor.commands === undefined  // ==> true
    this.cm.editor.save === undefined  // ==> true
  }

Is there something I'm missing, or is there another way to get the CodeMirror instance where the commands attribute is accessible?

Versions:

scniro commented 6 years ago

@jacktrades so I did a little looking into this, and it looks as if commands are found in the lib itself, not the instance...

commands are defined by the library itself

let cm = require('codemirror');
console.log(cm.commands); // appear to be defined here

Some of the commands, such as undo, indeed appear to be added to the instance itself for direct usage. In your case of save, though, could it be that you are missing a plugin of sorts?

Not defined by the core library, only referred to in key maps. Intended to provide an easy way for user code to define a save command.

ellamental commented 6 years ago

From what I can tell (mostly from this issue) it looks like the intention is that the user defines their own save command:

The save command (CodeMirror.commands.save) is missing by default, but Ctrl-S (and other keys in other keymaps) is bound to it. You can set it to your own save function with code like

CodeMirror.commands.save = function(insance) { /* ... do things with the given instance .. */ };

That seems rather straightforward if I were using CodeMirror directly, but I was unsure how to achieve that with this wrapper (other than the __proto__ hack I demonstrated).

Thanks for the pointer though, it looks like importing the codemirror lib and defining the save command in editorDidMount does the trick (not sure if there's a more idiomatic way to do this). Here's a full example:

import {Controlled as ReactCodeMirror} from 'react-codemirror2'
import CodeMirror from 'codemirror'

export class TextEditor extends React.Component {
  onSave = () => {
    console.log('Saved!');
  }

  render() {
    return <ReactCodeMirror editorDidMount={() => { CodeMirror.commands.save = this.onSave } />
  }

I'll close this issue since my problem is solved, but perhaps there's some documentation that could be helpful here? I'm not sure if this applies to any command other than save though, so maybe this issue is sufficient for the (presumably) small amount of people who will run into this.

scniro commented 6 years ago

@jacktrades Hm, I see more now what's going on here. Yea I hadn't seen any use cases like this beforehand and agree it's a little odd to not have a 1-stop shop to wire all this up. I can definitely look into if there are some clever ways do to this such as defineMode does. Would it be a little cleaner to not deal with editorDidMount at all and just wire it up via codemirror ASAP? I was thinking like this...

import {Controlled as ReactCodeMirror} from 'react-codemirror2'
import CodeMirror from 'codemirror'

export class TextEditor extends React.Component {

  constructor(props) {
    super(props);
    CodeMirror.commands.save = this.onSave
  }

  onSave = () => {
    console.log('Saved!');
  }

  render() {
    return <ReactCodeMirror />
  }
}

with this, by the time the instance is stood up within the wrapper it'll already know about save, as opposed to doing it after the fact via editorDidMount. I suppose there could also be no discernible difference to the end user, unless somewhere at initialization time within codemirror itself the behavior is altered if there exists a custom command definition - unsure!

I'm glad you were able to work around this and I'll check back should any API changes take place to make this easier to implement. Same with updating the docs...