0

我正在构建一个 Grapesjs 插件,并向按钮组件添加了一个“jscript”特征,该组件显示为代码镜像文本区域。这个想法是让用户能够编辑一些与按钮相关联的 JavaScript 代码。我似乎无法拦截 codemirror 区域的change事件,至少,不是正确的 codemirror 特定版本。

onEvent令人高兴的是,当我编辑代码镜像区域并更改焦点时,一个常规的“更改”事件会触发我插件中的Grapejs处理程序editor.TraitManager.addType('jcodemirror-editor', {}- 很好。然后我可以将 codemirror 区域的内容存储到特征中。

onEvent({ elInput, component, event }) {
    let code_to_run = elInput.querySelector(".CodeMirror").CodeMirror.getValue()
    component.getTrait('jscript').set('value', code_to_run);
},

但是,如果我们在代码镜像区域粘贴、退格或删除等,则永远不会发出常规的“更改”事件!

因此,我试图拦截更深层次的代码镜像特定“更改”事件,该事件通常通过拦截cm.on("change", function (cm, changeObj) {}并且更可靠地触发(不幸的是每次击键时也是如此)。如何连接此 codemirror 特定事件以触发通常的onEvent({ elInput, component, event }) {}代码?

我的https://jsfiddle.net/tcab/1rh7mn5b/中有一个解决方法,但想知道执行此操作的正确方法。

我的插件:

function customScriptPlugin(editor) {

    const codemirrorEnabled = true  // otherwise trait editor is just a plain textarea

    const script = function (props) {
        this.onclick = function () {
            eval(props.jscript)
        }
    };

    editor.DomComponents.addType("customScript", {
        isComponent: el => el.tagName == 'BUTTON' && el.hasAttribute && el.hasAttribute("data-scriptable"),
        model: {
            defaults: {
                traits: [
                    {
                        // type: 'text',
                        type: 'jcodemirror-editor',  // defined below
                        name: 'jscript',
                        changeProp: true,
                    }
                ],
                script,
                jscript: `let res = 1 + 3; console.log('result is', res);`,
                'script-props': ['jscript'],
            },
        },
    });

    editor.TraitManager.addType('jcodemirror-editor', {
        createInput({ trait }) {
            const el = document.createElement('div');
            el.innerHTML = `
                <form>
                    <textarea id="myjscript" name="myjscript" rows="14">
                    </textarea>
                </form>
            </div>
            `

            if (codemirrorEnabled) {
                const textareaEl = el.querySelector('textarea');
                var myCodeMirror = CodeMirror.fromTextArea(textareaEl, {
                    mode: "javascript",
                    lineWrapping: true,
                });

                // This is the 'more accurate' codemirror 'change' event
                // which is triggered key by key. We need it cos if we paste
                // or backspace or delete etc. in codemirror then the
                // regular 'change' event is never issued! But how do we get
                // this event to trigger the proper, usual 'onEvent' below?
                // Currently cheating and doing the onEvent work here with
                // this special handler.
                myCodeMirror.on("change", function (cm, changeObj) { // HACK
                    const component = editor.getSelected()
                    const code_to_run = myCodeMirror.getValue()
                    component.getTrait('jscript').set('value', code_to_run);
                    console.log('onEvent hack - (myCodeMirror change event) updating jscript trait to be:', code_to_run)

                })
            }

            return el;
        },

        // UI textarea & codemirror 'change' events trigger this function,
        // so that we can update the component 'jscript' trait property.
        onEvent({ elInput, component, event }) {
            let code_to_run

            if (codemirrorEnabled)
                code_to_run = elInput.querySelector(".CodeMirror").CodeMirror.getValue()
            else
                code_to_run = elInput.querySelector('textarea').value

            console.log('onEvent - updating jscript trait to be:', code_to_run)
            component.getTrait('jscript').set('value', code_to_run);
        }, // onEvent

        // Updates the trait area UI based on what is in the component.
        onUpdate({ elInput, component }) {
            console.log('onUpdate - component trait jscript -> UI', component.get('jscript'))

            if (codemirrorEnabled) {
                const cm = elInput.querySelector(".CodeMirror").CodeMirror
                cm.setValue(component.get('jscript'))

                // codemirror content doesn't appear till you click on it - fix with this trick
                setTimeout(function () {
                    cm.refresh();
                }, 1);
            }
            else {
                const textareaEl = elInput.querySelector('textarea');
                textareaEl.value = component.get('jscript')

                // actually is this even needed as things still update automatically without it?
                // textareaEl.dispatchEvent(new CustomEvent('change'));
            }

        }, // onUpdate

    }) // addType

    editor.BlockManager.add(
        'btnRegular',
        {
            category: 'Basic',
            label: 'Regular Button',
            attributes: { class: "fa fa-square-o" },
            content: '<button type="button">Click Me</button>',
        });

    editor.BlockManager.add(
        'btnScriptable',
        {
            category: 'Scriptable',
            label: 'Scriptable Button',
            attributes: { class: "fa fa-rocket" },
            content: '<button type="button" data-scriptable="true">Run Script</button>',
        });
}



const editor = grapesjs.init({
    container: '#gjs',
    fromElement: 1,
    height: '100%',
    storageManager: { type: 0 },
    plugins: ['gjs-blocks-basic', 'customScriptPlugin']
});

4

1 回答 1

0

根据关于集成外部 ui 组件onEvent的特征的官方 Grapesjs 文档,您可以通过调用手动触发事件this.onChange(ev)

因此,在createInput我继续拦截更可靠的myCodeMirror.on("change", ...事件并在该处理程序中触发onEvent手动即:

editor.TraitManager.addType('jcodemirror-editor', {
    createInput({ trait }) {
        const self = this  // SOLUTION part 1
        
        const el = document.createElement('div');
        el.innerHTML = `
            <form>
                <textarea id="myjscript" name="myjscript" rows="14">
                </textarea>
            </form>
        </div>
        `

        if (codemirrorEnabled) {
            const textareaEl = el.querySelector('textarea');
            var myCodeMirror = CodeMirror.fromTextArea(textareaEl, {
                mode: "javascript",
                lineWrapping: true,
            });

            myCodeMirror.on("change", function (cm, changeObj) {
                self.onChange(changeObj) // SOLUTION part 2
            })
        }

        return el;
    },
于 2021-02-07T23:08:56.333 回答