Hello all! In my previous post I went over how to set up a very basic rich text editor using CKEditor 5. The Classic Build that I used comes with the option to allow inserting images, but not out of the box.
If you try to upload an image, you will get this warning in the console along with a link to the docs:
filerepository-no-upload-adapter: Upload adapter is not defined.
Reading the description of the error in the link, you'll see that you need to configure an 'upload adapter' in order to get usage out of the image uploading feature.
You need to enable an upload adapter in order to be able to upload files. This warning shows up when FileRepository is being used without definining an upload adapter.
In this post I'll show you how I set up my own upload adapter in order to use the image uploading feature in CKEditor.
Set up the custom upload adapter
First I'm setting up an UploadAdapter
class. Inside of our constructor we need to a have a file loader instance that will control the process of reading and uploading files.
import { firebase } from '../utilities/firebase'
class UploadAdapter {
constructor(loader) {
this.loader = loader
}
}
export default UploadAdapter
Our upload adapter class also needs to have an upload()
method that returns a promise that is resolved when the file is successfully uploaded.
To make my code a little more dry, I created a variable for my firebase reference called uploadTask
. Then I'm using uploadTask.on()
to ultimately handle what I want to happen if the image is successfully uploaded to my firebase storage.
In .on()
we're passing in 4 parameters:
- a string that identifies an event (for us, we're checking if the state changed with
firebase.storage.TaskEvent.STATE_CHANGED
) - a callback function that gives you access to a snapshot
- a second callback for error handling
- a third callback for if the file upload is successful
Here's what my upload()
method looks like:
upload() {
return this.loader.file.then(
(file) =>
new Promise((resolve, reject) => {
// firebase reference
let uploadTask = firebase.storage().ref()
.child(`images/${file.name}`)
.put(file)
uploadTask.on(
firebase.storage.TaskEvent.STATE_CHANGED,
(snapshot) => {
/* snapshot with info about
the upload progress & metadata */
},
(error) => {
// error handling
},
() => {
// upload successful
}
)
})
)
}
The snapshot contains info about the upload progress and other metadata, which can be used if you'd like to display a progress bar to the user. You can use the error callback to handle any errors that may occur during the upload process.
Finally, we want to tell firebase what to do if the image upload is successful. In our last callback function, we want to grab the download URL that was created and tell CKEditor to use that URL to display the image within the editor.
uploadTask.snapshot.ref
.getDownloadURL()
.then((downloadURL) => {
resolve({
default: downloadURL
})
})
Ultimately, your UploadAdapter file should end up looking like this:
import { firebase } from '../utilities/firebase'
class UploadAdapter {
constructor(loader) {
this.loader = loader
}
upload() {
return this.loader.file.then(
(file) =>
new Promise((resolve, reject) => {
// firebase reference
let uploadTask = firebase.storage().ref()
.child(`images/${file.name}`)
.put(file)
uploadTask.on(
firebase.storage.TaskEvent.STATE_CHANGED,
(snapshot) => {
/* snapshot with info about
the upload progress & metadata */
},
(error) => {
// error handling
},
() => {
// upload successful
uploadTask.snapshot.ref
.getDownloadURL()
.then((downloadURL) => {
resolve({
default: downloadURL
})
})
}
)
})
)
}
}
export default UploadAdapter
Import our custom upload adapter into our CKEditor
Import UploadAdapter into your text editor component where you are using CKEditor. The CKEditor component takes a prop called onInit
, a function called when the editor is initialized. This gives us access to the initialized editor
.
To specify that we want to use our own custom upload adapter, we want to utilize the FileRepository
plugin. FileRepository
gives us the prop .createUploadAdapter
, which is a function that takes in a file loader instance and returns a new upload adapter instance.
import React, { useState } from 'react'
import CKEditor from '@ckeditor/ckeditor5-react'
import ClassicEditor from '@ckeditor/ckeditor5-build-classic'
import UploadAdapter from './UploadAdapter'
const TextEditor = ({ onSubmit }) => {
const [body, setBody] = useState('')
const handleSubmit = (e) => {
e.preventDefault()
onSubmit({ body })
}
return (
<form onSubmit={handleSubmit}>
<CKEditor
// here's where we are using our custom adapter
onInit={(editor) => {
editor.plugins.get('FileRepository')
.createUploadAdapter = (loader) => {
return new UploadAdapter(loader)
}
}}
editor={ClassicEditor}
onChange={(event, editor) => {
const data = editor.getData()
setBody(data)
}}
/>
<button type='submit'>Submit</button>
</form>
)
}
export default TextEditor
Now you should be allowed to upload images in your editor. Out of the box it allows you to upload, drag and paste images into the editor. It also gives you the option to let your images be inline or block, and include an image caption.
Thanks for reading! 😊
Top comments (3)
Great Article. I have a question about how to delete the image uploaded in firebase when users decide not to use that image in the editor. Do we have an onChange event that triggers when the user hits BackSpace (to remove image)? Or we have to go to the firebase and remove it manually.
This helped me a lot. But could you please me out with what to do when a user uploads an image on editor and then decides to discard that image. Now this image is being stored on the firebase storage. How to delete the images in such a case?
Nice...
Please provide git repos also...
It will be helpful..
THANKS IN ADVANCE....