CKEditor is an awesome opensource project that offers tons of tools similar to Microsoft word. I have been using this editor on the website that I recently created (https://www.codingnotesonline.com/). However, for the image upload option in CKEditor, it requires a server to store the images. The examples mentioned in the documents are specifically based on PHP for the backend side and you can find more examples of similar implementation in Nodejs. The problem was that I was using firebase for all the backend worked and didn't find a proper example to implement it with firebase.
How to Implement CKEditor Image upload with Firebase in React ?
I have used firebase upload functions and added in custom adapter function of CKEditor.
Firebase(upload function):(https://firebase.google.com/docs/storage/web/upload-files)
CKEditor(custom adapter):(https://ckeditor.com/docs/ckeditor5/latest/framework/guides/deep-dive/upload-adapter.html)
In the below example, MyUploadAdapter is the custom adapter in which we use a firebase upload function that returns a URL, which is a link to an image stored in the firebase server. This link is then used by the CKEditor to display it on the page.
Firebase also offers several upload stages that you can use to show the progress of image upload to the user.
Below is a complete working example that you can implement in your project.
import React from "react";
import CKEditor from "@ckeditor/ckeditor5-react";
import ClassicEditor from "@ckeditor/ckeditor5-build-classic";
//import firbase from your directory
class MyUploadAdapter {
constructor(loader) {
this.loader = loader;
}
// Starts the upload process.
upload() {
return this.loader.file.then(
file =>
new Promise((resolve, reject) => {
let storage = firebase.storage().ref();
let uploadTask = storage
.child(file.name)
.put(file, metadata);
uploadTask.on(
firebase.storage.TaskEvent.STATE_CHANGED, // or 'state_changed'
function(snapshot) {
// Get task progress, including the number of bytes uploaded and the total number of bytes to be uploaded
var progress =
(snapshot.bytesTransferred / snapshot.totalBytes) * 100;
console.log("Upload is " + progress + "% done");
switch (snapshot.state) {
case firebase.storage.TaskState.PAUSED: // or 'paused'
console.log("Upload is paused");
break;
case firebase.storage.TaskState.RUNNING: // or 'running'
console.log("Upload is running");
break;
}
},
function(error) {
// A full list of error codes is available at
// https://firebase.google.com/docs/storage/web/handle-errors
// eslint-disable-next-line default-case
switch (error.code) {
case "storage/unauthorized":
reject(" User doesn't have permission to access the object");
break;
case "storage/canceled":
reject("User canceled the upload");
break;
case "storage/unknown":
reject(
"Unknown error occurred, inspect error.serverResponse"
);
break;
}
},
function() {
// Upload completed successfully, now we can get the download URL
uploadTask.snapshot.ref
.getDownloadURL()
.then(function(downloadURL) {
// console.log("File available at", downloadURL);
resolve({
default: downloadURL
});
});
}
);
})
);
}
}
class App extends Component {
render() {
return (
<div className="App">
<h2>Using CKEditor 5 build in React</h2>
<CKEditor
editor={ ClassicEditor }
data="<p>Hello from CKEditor 5!</p>"
onInit={editor => {
editor.plugins.get("FileRepository").createUploadAdapter = loader => {
return new MyUploadAdapter(loader);
};
}}
onChange={ ( event, editor ) => {
const data = editor.getData();
console.log( { event, editor, data } );
} }
onBlur={ ( event, editor ) => {
console.log( 'Blur.', editor );
} }
onFocus={ ( event, editor ) => {
console.log( 'Focus.', editor );
} }
/>
</div>
);
}
}
This is a boilerplate code that you can use and make changes as per your needs. I hope this would help you and saved a lot of your time finding the solution to it.
Top comments (10)
Hi, very nice article.
I just have a question.
Say the user adds an image, but regret the action and remove it from the editor.
The image will be uploaded to storage either way. How can we revert this action / delete the unused image from storage? or else, we might run into the problem of which we fill up our storage with unused images :p
I made a working solution to this. I'm pushing the file.name from your module into a root array, and taking substrings from the editor.getData() which only consists of image names. On create, I check both these arrays up against each other, and delete the images from storage that haven't been used.
This way, all the images will be temporarily until the user clicks save.
One edge case if if the user leaves the editor before the save is pressed. This also needs to be handled by deleting all images that has been uploaded temporarily.
Hi Stephan, I am stuck here. Could you please tell me how you achieved this using a code snippet?
Thank You.
Hi Stephan. could you please tell me file repo. or any git hub link for this. please.
Here is the code with Firebase 9
class MyUploadAdapter {
constructor(loader) {
this.loader = loader;
}
// Starts the upload process.
upload() {
return this.loader.file.then(
(file) =>
new Promise((resolve, reject) => {
let storage = getStorage();
uploadBytes(
ref(storage,
/${"Dial & Dine"}/${new Date().getTime()}
),file
)
.then((snapshot) => {
return getDownloadURL(snapshot.ref);
})
.then((downloadURL) => {
resolve({
default: downloadURL,
});
}).catch((error)=>{
reject(error.message);
})
})
);
}
}
does anyone know what firebase to import from your directory??
My code:
import { initializeApp } from "firebase/app";
import { getAnalytics } from "firebase/analytics";
import { getStorage } from "firebase/storage";
export const firebaseConfig = {
apiKey: "XXXXXXXXXXXXXXXXXXXXXXXXXXXX",
authDomain: ""XXXXXXXXXXXXXXXX",",
projectId: "XXXXXXXXXXXXXXXX",",
storageBucket: "XXXXXXXXXXXXXXXX",,
messagingSenderId: "XXXXXXXXXXXXXXXX",,
appId: "XXXXXXXXXXXXXXXX",,
measurementId: "XXXXXXXXXXXXXXXX",
};
// Initialize Firebase
export const firebase = initializeApp(firebaseConfig);
export const storage = getStorage(firebase);
Thank you so much.
I apply this but my page is load when image is upload
It was nice..
It will be helpful if you provide git repos..
thanks in advance..
Thank you so much.
if the code still does not work try using onReady instead of onInit.