Ju99ernaut / grapesjs-script-editor

Edit or attach script to selected component
MIT License
26 stars 15 forks source link

Access to codemirror and its addons #2

Closed abulka closed 3 years ago

abulka commented 3 years ago

I want to add extra shortcut keys to invoke codemirror addons whilst using grapesjs-script-editor. I also want to be able to programmatically insert text into the the script editor.

Details

Add-ons

Since this plugin exposes a codeViewOptions object I tried to add some extra shortcut keys e.g. for commenting out lines and duplicating lines etc.

Whilst the hotkeys are triggering ok, the commands are not working - failing because the specified commands don't exist?

codeViewOptions: {
  extraKeys: {
     'Cmd-/': function (cm) { cm.execCommand('toggleComment') }, // FAILS
     'Cmd-1': function (cm) { cm.execCommand('commentRange') }, // FAILS though built into grapesjs formatting.js?
     'Cmd-2': function (cm) { cm.execCommand('transposeChars') }, // WORKS
     'Ctrl-X': function (cm) { cm.execCommand('deleteLine') }, // WORKS
  }
},

I tried importing <script src="node_modules/codemirror/addon/comment/comment.js"></script> but am wondering if this has any effect since Grapesjs seems to get its extra codemirror commands from some special module https://github.com/artf/codemirror-formatting/blob/master/formatting.js.

Can you advise on how to use codemirror addons within the grapesjs-script-editor esp. the commenting command?

Accessing CodeMirror

I also want to define a duplicate line command which I got from https://github.com/codemirror/CodeMirror/blob/master/keymap/sublime.js#L327 but which requires access to the Codemirror object e.g. CodeMirror.commands and CodeMirror.Pos - is there any way to get to Codemirror?

Accessing the codemirror instance cm

Finally, I want to add UI elements to the top of the script editor, which I can happily do with e.g.

pluginsOpts: {
    'grapesjs-script-editor': {
        commandAttachScript: {
            getPreContent() {
              return '<button>Insert Example Code</button>'
            },

I plan to add listener functions to those buttons to insert script fragments and examples into the script editor using codemirror functions like

cm.replaceRange(text, CodeMirror.Pos(line))
cm.setValue(text)

I even have a cool function to insert text at the current cursor position

function insertTextAtCursorCodeMirror(cm, text) {
  var doc = cm.getDoc();
  var cursor = doc.getCursor();
  doc.replaceRange(text, cursor);
}

Can I get access to cm from my listener which would be defined as part of getPreContent()? That would be so powerful!

Ju99ernaut commented 3 years ago

Currently the Codemirror instance that is used by the script editor isn't exposed, I think exposing it will allow for most of the stated customizations, getPreContent and getPostContent have been on the roadmap for implementation so I'll also look into them.

Ju99ernaut commented 3 years ago

Actually you can get the CodeMirror instance by using:

editor.Commands.get('edit-script').getCodeViewer()

As of CodeMirror itself I'm not sure if it's exposed by grapesjs, if it's not then you can request that on the main repo.

Edit: Looks like CodeMirror isn't exposed: https://github.com/artf/grapesjs/blob/dev/src/code_manager/model/CodeMirrorEditor.js

Ju99ernaut commented 3 years ago

For reference getPreContent and getPostContent are also already implemented, these functions must return a string or HTMLElement:

pluginsOpts: {
    'grapesjs-script-editor': {
        commandAttachScript: {
            getPreContent() {},
            getPostContent() {}
        }
}
abulka commented 3 years ago

Thanks, getting the CodeMirror instance like that worked, though the actual cm instance needs .editor thus

let cm = editor.Commands.get('edit-script').getCodeViewer().editor

Working example of snippets button within editor

This will add a button to the script-editor

getPreContent() { 
  return '<p>Snippets</p><button id="snippet1">snippet1</button><p></p>'
},

this code inside showCustomCode(target) will then hook up that snippet button to change the script-editor contents

const snippet1 = '...'
document.getElementById('snippet1').addEventListener('click', function () {
    cm.setValue(snippet1)
})

I still need to get to the Codemirror object in order to access things like CodeMirror.Pos so that I can do more advanced snippets, but this is a great start!

Ju99ernaut commented 3 years ago

Awesome stuff, you can also add the events within getPreContent:

getPreContent() { 
  const btn = editor.$('<p>Snippets</p><button id="snippet1">snippet1</button><p></p>');
  btn.find('snippet1').click(function() { 
     //...
  });
  //or
  btn.find('snippet1').on('click', function() { 
     //...
  });
  return btn.get(0);
},

For Codemirror itself you can ask for it to be exposed on the main repo https://github.com/artf/grapesjs or import the library yourself.

abulka commented 3 years ago

Would appreciate any thoughts on the first half of this issue?

"Can you advise on how to use codemirror addons within the grapesjs-script-editor esp. the commenting command?"

I can't get commenting to work, for example.

Ju99ernaut commented 3 years ago

Seems to me the addons are imported correctly so maybe those addons are deactivated by default so try making them active:

codeViewOptions: {
  //...
  toggleComment: true,
  commentRange: true,
  //...
},
abulka commented 3 years ago

I added those enabling lines but it made no difference. I made a minimal https://jsfiddle.net/tcab/hzngLb6o/

I've tried tracing into codemirror but its pretty hairy in there... I think the commands aren't being found for some reason.

Ju99ernaut commented 3 years ago

Unfortunately, I'm not really sure as this issue is a bit complicated to debug, maybe cm.execCommand('...') is not the proper way of invoking such extensions, given they are not built into Codemirror

abulka commented 3 years ago

In past projects I've found that either of these techniques work:

'Cmd-/': function (cm) { cm.execCommand('toggleComment') }, // technique 1
'Cmd-1': function (cm) { cm.toggleComment() }, // technique 2

however it seems you are right re invoking the Grapesjs bundled commentRange - it must be done using technique 2 and not via cm.execCommand. Furthermore, it takes parameters. Thus I was able to get some commenting working, as long as you select the area you want commented:

'Cmd-1': function (cm) { cm.commentRange(true, cm.getCursor(true), cm.getCursor(false)) },  // comment
'Cmd-2': function (cm) { cm.commentRange(false, cm.getCursor(true), cm.getCursor(false)) },  // uncomment

I've created an issue at Grapesjs https://github.com/artf/grapesjs/issues/3286 - perhaps someone there can help further with 3rd party add-ons esp. re exposing the CodeMirror function itself, which as you say, is not currently exposed and is needed for lots of things, including calls like CodeMirror.Pos and CodeMirror.cmpPos etc.

Come to think of it, I wonder if importing Codemirror ourselves and avoiding the built in codemirror would be possible? Then we would have full control and be able to use all the add-ons in the script editor like auto-complete etc!

Ju99ernaut commented 3 years ago

I think importing Codemirror ourselves is possible, using the built in version just helps us avoid importing the same library twice.