crac's avatar
Level 1

Laravel 9 + CKEditor 5 (from source)

I'm a relative newbie (to Laravel), trying to get CKEditor working, and it's throwing up several problems which I suspect are related to how I'm trying to configure the editor.

What I am trying to do is to have a source build CKEditor, because I need to add some custom plugins once I've got it working. The problem I'm having is that I can't work out how to translate the vanilla webpack.js code here: https://ckeditor.com/docs/ckeditor5/latest/installation/getting-started/quick-start.html#building-the-editor-from-source into something that will work with laravel-mix.

I've got mix working registering a number of other npm plugins, but this one seems to require something a bit more complex in terms of how it is set up.

Strangely I can't find much in the way of clues on the web - and I've been searching for days now!

The code I'm running at the moment - in webpack.mix.js:

/**
 * CK Editor Config
 */
const CKEStyles = require('@ckeditor/ckeditor5-dev-utils').styles;
const CKERegex = {
    svg: new RegExp('ckeditor5-[^/\]+[/\]theme[/\]icons[/\][^/\]+\.svg$'),
    css: new RegExp('ckeditor5-[^/\]+[/\]theme[/\].+\.css$')
};

/**
 * Webpack Config for CK Editor
 */
mix.webpackConfig({
    module: {
        rules: [
            {
                test: CKERegex.svg,
                use: ['raw-loader']
            },
            {
                test: CKERegex.css,
                use: [
                    {
                        loader: 'style-loader',
                        options: {
                            singleton: true
                        }
                    },
                    {
                        loader: 'postcss-loader',
                        options: {
                            postcssOptions: CKEStyles.getPostCssConfig({
                                themeImporter: {
                                    themePath: require.resolve('@ckeditor/ckeditor5-theme-lark')
                                },
                                minify: true
                            })
                        }
                    }
                ]
            }
        ]
    }
});

Mix.listen('configReady', webpackConfig => {

    const rules = webpackConfig.module.rules;
    const targetSVG = new RegExp('(\.(png|jpe?g|gif|webp)$|^((?!font).)*\.svg$)');
    const targetFont = new RegExp('(\.(woff2?|ttf|eot|otf)$|font.*\.svg$)');
    const targetCSS = new RegExp('\.css$');

    // Exclude CK Editor regex from mix's default rules
    for (let rule of rules) {
        if (rule.test.toString() === targetSVG.toString()) {
            rule.exclude = CKERegex.svg;
        } else if (rule.test.toString() === targetFont.toString()) {
            rule.exclude = CKERegex.svg;
        } else if (rule.test.toString() === targetCSS.toString()) {
            rule.exclude = CKERegex.css;
        }
    }
});

mix.js('resources/js/ckeditor.js', 'public/js');

Then ckeditor.js:

import ClassicEditor from '@ckeditor/ckeditor5-editor-classic/src/classiceditor';
import Essentials from '@ckeditor/ckeditor5-essentials/src/essentials';
import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph';
import Bold from '@ckeditor/ckeditor5-basic-styles/src/bold';
import Italic from '@ckeditor/ckeditor5-basic-styles/src/italic';

var ready = (callback) => {
    if (document.readyState != "loading") callback();
    else document.addEventListener("DOMContentLoaded", callback);
}

ready(() => {

    let config = Object({
        plugins: [ Essentials, Paragraph, Bold, Italic ],
        toolbar: [ 'bold', 'italic' ]
    });

    ClassicEditor
        .create($('.wysiwyg'), config)
        .then(editor => {
            console.log('Editor was initialized', editor);
        })
        .catch(error => {
            console.error(error.stack);
        });

});

If I run it exactly like this I get an error:

ckeditor.js:96228 TypeError: Cannot read properties of null (reading 'getAttribute')
    at IconView._updateXMLContent (ckeditor.js:64343:24)
    at IconView.render (ckeditor.js:64319:8)
    at IconView.<anonymous> (ckeditor.js:81534:32)
    at IconView.fire (ckeditor.js:78925:30)
    at <computed> [as render] (ckeditor.js:81538:16)
    at ViewCollection._renderViewIntoCollectionParent (ckeditor.js:73410:9)
    at ViewCollection.<anonymous> (ckeditor.js:73271:9)
    at ViewCollection.fire (ckeditor.js:78925:30)
    at ViewCollection.addMany (ckeditor.js:74690:9)
    at ViewCollection.add (ckeditor.js:74655:15)

which seems to be related to paths to svg files used in the toolbar.

If I remove the items from the toolbar config line (so no svg files are required) I get this:

ckeditor.js:96228 CKEditorError: datacontroller-init-non-existent-root
Read more: https://ckeditor.com/docs/ckeditor5/latest/support/error-codes.html#error-datacontroller-init-non-existent-root
    at DataController.init (ckeditor.js:7114:10)
    at DataController.<anonymous> (ckeditor.js:81534:32)
    at DataController.fire (ckeditor.js:78925:30)
    at <computed> [as init] (ckeditor.js:81538:16)
    at ckeditor.js:6470:31

which seems to be to do with finding the content in the textareas I want to convert to CKEditors.

So I've got at least two errors going on, and I'm stumbling around trying to work it out.

Any suggestions appreciated!

0 likes
2 replies
vincent15000's avatar

I don't know ... but I think you have an error here.

Mix.listen() should be mix.listen()
crac's avatar
Level 1

@vincent15000 I wondered about that, but I think MIx is a different object from the mix constant that I'm declaring - but I can't find any clue about it or the listen method in any documentation for any version ...

I found that bit of code

I've just tried rewriting that bit using mix.override instead:

mix.override(webpackConfig => {

    const rules = webpackConfig.module.rules;
    const targetSVG = new RegExp('(\.(png|jpe?g|gif|webp)$|^((?!font).)*\.svg$)');
    const targetFont = new RegExp('(\.(woff2?|ttf|eot|otf)$|font.*\.svg$)');
    const targetCSS = new RegExp('\.css$');

    // Exclude CK Editor regex from mix's default rules
    for (let rule of rules) {
        if (rule.test.toString() === targetSVG.toString()) {
            rule.exclude = CKERegex.svg;
        } else if (rule.test.toString() === targetFont.toString()) {
            rule.exclude = CKERegex.svg;
        } else if (rule.test.toString() === targetCSS.toString()) {
            rule.exclude = CKERegex.css;
        }
    }

});

which seems to be an improvement in terms of it now actually runs this bit of code, but I'm still getting the same errors when trying to load the editor.

I've had another go now, and got it so it is actually now rewriting the rules:

mix.override(webpackConfig => {

    const rules = webpackConfig.module.rules;
    const targetSVG = '/(\.(png|jpe?g|gif|webp|avif)$|^((?!font).)*\.svg$)/';
    const targetFont = '/(\.(woff2?|ttf|eot|otf)$|font.*\.svg$)/';
    const targetCSS = '/\.p?css$/';

    // Exclude CK Editor regex from mix's default rules
    for (let rule of rules) {

        if (rule.test.toString() === targetSVG.toString()) {
            if (Array.isArray(rule.exclude)) {
                rule.exclude.push(CKERegex.svg);
            } else {
                rule.exclude = [CKERegex.svg];
            }
        } else if (rule.test.toString() === targetFont.toString()) {
            if (Array.isArray(rule.exclude)) {
                rule.exclude.push(CKERegex.svg);
            } else {
                rule.exclude = [CKERegex.svg];
            }
        } else if (rule.test.toString() === targetCSS.toString()) {
            if (Array.isArray(rule.exclude)) {
                rule.exclude.push(CKERegex.css);
            } else {
                rule.exclude = [CKERegex.css];
            }
        }

    }

});

so this is now adding the exclude rules, but to be honest I'm not really sure what they do or why they should be useful.

Still fumbling around ...

Please or to participate in this conversation.