CKEditor5 With Custom Image Uploader in React

abidullah786

ABIDULLAH786

Posted on September 21, 2024

CKEditor5 With Custom Image Uploader in React

Spent almost 2 days figuring out a proper way to use CKEditor5 image uploader. I’ve tried ckFinder, SimpleUploader, etc. but none of them worked maybe because none of the documentations made any sense to me 😂. Luckily, I found a Stack Overflow conversation and somehow got what I needed working with just some minor tweaks.

My Goal

Every time a user copy and paste, drag and drop, or upload an image into my text editor, It will trigger an API that saves the image into an S3 bucket and returns the image S3 URL, then embeds that image back to the text editor.

Installation:

Install CKEditor5 to react here.

Implementation:

Editor Code

import React, { useState } from "react";
import { CKEditor } from "@ckeditor/ckeditor5-react";
import ClassicEditor from "@ckeditor/ckeditor5-build-classic";

function Editor() {
    const [editorData, setEditorData] = useState();

    return (
        <div>
            <CKEditor
                editor={ClassicEditor}
                data={editorData}
                onReady={(editor) => {
                    console.log("Editor is ready to use!", editor);
                }}
                onChange={(event, editor) => {
                    const data = editor.getData();
                    setEditorData(data);
                    console.log({ event, editor, data });
                }}
                onBlur={(event, editor) => {
                    console.log("Blur.", editor);
                    console.log(editorData)
                }}
                onFocus={(event, editor) => {
                    console.log("Focus.", editor);
                }}
            />
        </div>
    );
}

export default Editor;
Enter fullscreen mode Exit fullscreen mode

output

basic editor output

Till now you can face the issue (as in below image) while uploading the image in editor.

Image uploading warning

due to this issue your image will not attach into editor. to resolve this issue you have to add the image toolbar plugin in editor config.

Here is the editor updated code:

import React, { useState } from "react";
import { CKEditor } from "@ckeditor/ckeditor5-react";
import ClassicEditor from "@ckeditor/ckeditor5-build-classic";

function Editor() {
    const [editorData, setEditorData] = useState();

    return (
        <div>
            <CKEditor
                editor={ClassicEditor}
                data={editorData}
                onReady={(editor) => {
                    console.log("Editor is ready to use!", editor);
                }}
                onChange={(event, editor) => {
                    const data = editor.getData();
                    setEditorData(data);
                    console.log({ event, editor, data });
                }}
                onBlur={(event, editor) => {
                    console.log("Blur.", editor);
                    console.log(editorData)
                }}
                onFocus={(event, editor) => {
                    console.log("Focus.", editor);
                }}
                config={{
                    image: {
                        toolbar: [
                            'imageTextAlternative',
                            'toggleImageCaption',
                            'imageStyle:inline',
                            'imageStyle:block',
                            'imageStyle:side',
                        ]
                    },
                }}
            />
        </div>
    );
}

export default Editor;
Enter fullscreen mode Exit fullscreen mode

Now you will see the image after inserting it in editor.

Uploading Image to server

Till now you must noticed it that every thing in the we are adding or writing in saved into single state variable. the problem will arise when you want to to have the Full Stack application Editor will all feature. Because getting all content of editor getting in on place cause the issue of extracting the image/video files before saving the text in database, as you know it is not recommended to store the files into database.

Here the challenge of extracting the files form single object arise. because we don't know at which line and where the files are added/attached in the editor.

to overcome this issue we will use the custom image uploader adopter which will take the file at the time of attaching to editor and send it to server and get the result/url back and automatically it will render that uploaded url into editor

here is the code for that

// CustomeUoload.js

class CustomUploadAdapter {
    constructor(loader) {
        // The file loader instance to use during the upload.
        this.loader = loader;
    }

    upload() {
        return this.loader.file.then(file => new Promise((resolve, reject) => {
            // Customize your upload logic here.

            // but for siplicity i am using the dummy url and evrytime sending back the same request
            const url = "https://upload.wikimedia.org/wikipedia/commons/thumb/7/77/Google_Images_2015_logo.svg/220px-Google_Images_2015_logo.svg.png"
            resolve({ default: url })
        }));
    }

    abort() {
        // Abort the upload process if needed.
    }
}

export default function CustomUploadAdapterPlugin(editor) {
    console.log(editor)
    editor.plugins.get('FileRepository').createUploadAdapter = (loader) => {
        return new CustomUploadAdapter(loader);
    };
}
Enter fullscreen mode Exit fullscreen mode

Now you have to call the above CustomUploadAdapterPlugin component into your editor and add it into config as follows.

...
   config={{
      ...
      extraPlugins: [CustomUploadAdapterPlugin],
   }}
...
Enter fullscreen mode Exit fullscreen mode

Actual Code

Here’s the full code, no more talking. Enjoy! but not too much 😊.
editor.js file

// editor.js
import React, { useState } from "react";
import { CKEditor } from "@ckeditor/ckeditor5-react";
import ClassicEditor from "@ckeditor/ckeditor5-build-classic";
import CustomUploadAdapterPlugin from "./customUpload";

function Editor() {
    const [editorData, setEditorData] = useState();

    return (
        <div>
            <CKEditor
                editor={ClassicEditor}
                data={editorData}
                onReady={(editor) => {
                    console.log("Editor is ready to use!", editor);
                }}
                onChange={(event, editor) => {
                    const data = editor.getData();
                    setEditorData(data);
                    console.log({ event, editor, data });
                }}
                onBlur={(event, editor) => {
                    console.log("Blur.", editor);
                    console.log(editorData)
                }}
                onFocus={(event, editor) => {
                    console.log("Focus.", editor);
                }}
                config={{
                    image: {
                        toolbar: [
                            'imageTextAlternative',
                            'toggleImageCaption',
                            'imageStyle:inline',
                            'imageStyle:block',
                            'imageStyle:side',
                        ]
                    },
                    extraPlugins: [CustomUploadAdapterPlugin],
                }}
            />
        </div>
    );
}

export default Editor;
Enter fullscreen mode Exit fullscreen mode

costomupload.js file

// costomupload.js

class CustomUploadAdapter {
    constructor(loader) {
        // The file loader instance to use during the upload.
        this.loader = loader;
    }

    upload() {
        return this.loader.file.then(file => new Promise((resolve, reject) => {
            // Customize your upload logic here.

            // but for siplicity i am using the dummy url and evrytime sending back the same request
            const url = "https://upload.wikimedia.org/wikipedia/commons/thumb/7/77/Google_Images_2015_logo.svg/220px-Google_Images_2015_logo.svg.png"
            resolve({ default: url })
        }));
    }

    abort() {
        // Abort the upload process if needed.
    }
}

export default function CustomUploadAdapterPlugin(editor) {
    console.log(editor)
    editor.plugins.get('FileRepository').createUploadAdapter = (loader) => {
        return new CustomUploadAdapter(loader);
    };
}
Enter fullscreen mode Exit fullscreen mode

Now open the terminal type below command if you’re using create-react-app

npm start

Enter fullscreen mode Exit fullscreen mode

And here is our final result

final resutl

Conclusion

Thanks for reading my article ❤, if you have any problem just comment below and I will help you.

If the post was useful to you, leave me a clap 👏 and follow me, it helps me a lot. Thank you ❤️

Checkout my Blogger, Twitter, Linkedin and GitHub for more amazing content!

💖 💪 🙅 🚩
abidullah786
ABIDULLAH786

Posted on September 21, 2024

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related