-
Notifications
You must be signed in to change notification settings - Fork 3.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
How to insert images by uploading to the server instead of Base64 encoding the images? #1089
Comments
@benbro I read #995 and tried the imageHandler, but it's quite hard for me to revise my .js to allow addition of handler as @jackmu95's commits files do since I am new to js. Besides, the library that I include is
|
@AbrahamChin It's not that easy to include the changes of my PR into the production builds so I made a Demo to showcase how it would work when my PR get's merged. |
@jackmu95 thx, if I implemented it as you did, does it mean that images could be firstly posted to server and then display in the editor? thus base64 encoding is substituted by a URL directed to the server image upload directory? |
@AbrahamChin As you can see in my example the image is uploaded to a server (in my case Imgur) and the response from the server returns the image URL. This URL needs to be passed to the |
I tested the codepen script by dragging and dropping an image and after inspecting the image element, it appears to be a base64 image. |
请教下,选择图片后,出来的也是整个内容,图片路径是一串很长的字符串,如何单独把文件类型上传到服务器呢? |
@lpp288 你的问题解决了吗? 求指教 |
@gpyys 没呢,那个就是base64,可以试下react-lz-editor |
@gpyys @lpp288 Replace the default image handler with your own's
the offical image handler is here:
As the code , you may use any ajax lib to upload the file and create an image blot fill the src by url. |
My code use axios.
|
@magicdvd 3Q |
@magicdvd |
@zzkkui I think you have to be slightly more specific than that. :D If you're just running that little block of code as-is, then you'll get an undefined for |
@lpp288 @gpyys 你们这个问题 都解决没 有没有什么好的办法 |
I solved the problem that upload image with url. const editor = new Quill('#quill-editor', {
bounds: '#quill-editor',
modules: {
toolbar: this.toolbarOptions
},
placeholder: 'Free Write...',
theme: 'snow'
});
/**
* Step1. select local image
*
*/
function selectLocalImage() {
const input = document.createElement('input');
input.setAttribute('type', 'file');
input.click();
// Listen upload local image and save to server
input.onchange = () => {
const file = input.files[0];
// file type is only image.
if (/^image\//.test(file.type)) {
saveToServer(file);
} else {
console.warn('You could only upload images.');
}
};
}
/**
* Step2. save to server
*
* @param {File} file
*/
function saveToServer(file: File) {
const fd = new FormData();
fd.append('image', file);
const xhr = new XMLHttpRequest();
xhr.open('POST', 'http://localhost:3000/upload/image', true);
xhr.onload = () => {
if (xhr.status === 200) {
// this is callback data: url
const url = JSON.parse(xhr.responseText).data;
insertToEditor(url);
}
};
xhr.send(fd);
}
/**
* Step3. insert image url to rich editor.
*
* @param {string} url
*/
function insertToEditor(url: string) {
// push image url to rich editor.
const range = editor.getSelection();
editor.insertEmbed(range.index, 'image', `http://localhost:9000${url}`);
}
// quill editor add image handler
editor.getModule('toolbar').addHandler('image', () => {
selectLocalImage();
}); |
@Q-Angelo https://segmentfault.com/a/1190000009877910 我是看的这个 解决的 |
I solved this for now with listener that looks for images added. function quillFormImgListener (formSelector) { // eslint-disable-line no-unused-vars
var $form = $(formSelector)
$form.on('blur change keyup paste input', '[contenteditable]', function () {
if (noUpdateInProgress) {
var $images = $('.ql-editor img')
$images.each(function () {
var imageSrc = $(this).attr('src')
if (imageSrc && imageSrc[0] === 'd') {
console.log('Starting image upload...')
noUpdateInProgress = false
disableSubmit($form)
uploadImageToImgurAndReplaceSrc($(this), enableSubmit)
}
})
}
})
}
function uploadImageToImgurAndReplaceSrc($image, callbackFunc) {
var imageBase64 = $image.attr('src').split(',')[1];
$.ajax({
url: 'https://api.imgur.com/3/image',
type: 'post',
data: {
image: imageBase64
},
headers: {
Authorization: 'Client-ID ' + clientId
},
dataType: 'json',
success: function (response) {
$image.attr('src', response.data.link.replace(/^http(s?):/, ''));
callbackFunc();
}
});
} |
Can some one suggest some text editors(eg: ckeditors) which supports image upload by (base64 image conversion) |
@TaylorPzreal This works in my project, thanks bro~ |
Angular test editor |
This is what I used for my project. Only complaint I have is I couldn't really figure out how to show some progress or notify the user that the img is uploading. Anyone got tips for that? For now I just disable the editor and then re-enable it once the upload is complete. const editor_options = {
theme: 'snow',
modules: {
toolbar: {
container: [['bold', 'italic', 'underline', 'strike'], ['link', 'image', 'video']],
handlers: { image: quill_img_handler },
},
},
};
function quill_img_handler() {
let fileInput = this.container.querySelector('input.ql-image[type=file]');
if (fileInput == null) {
fileInput = document.createElement('input');
fileInput.setAttribute('type', 'file');
fileInput.setAttribute('accept', 'image/png, image/gif, image/jpeg, image/bmp, image/x-icon');
fileInput.classList.add('ql-image');
fileInput.addEventListener('change', () => {
const files = fileInput.files;
const range = this.quill.getSelection(true);
if (!files || !files.length) {
console.log('No files selected');
return;
}
const formData = new FormData();
formData.append('file', files[0]);
this.quill.enable(false);
axios
.post('/api/image', formData)
.then(response => {
this.quill.enable(true);
this.quill.editor.insertEmbed(range.index, 'image', response.data.url_path);
this.quill.setSelection(range.index + 1, Quill.sources.SILENT);
fileInput.value = '';
})
.catch(error => {
console.log('quill image upload failed');
console.log(error);
this.quill.enable(true);
});
});
this.container.appendChild(fileInput);
}
fileInput.click();
} |
Not too familiar with Axios but with a regular XMLHttpRequest(), you can add an eventlistener to the upload, e.g.
I have this working well, but |
Hi all, this is how I solved this in the end: quill.on("text-change", async function(delta, oldDelta, source) {
const imgs = Array.from(
container.querySelectorAll('img[src^="data:"]:not(.loading)')
);
for (const img of imgs) {
img.classList.add("loading");
img.setAttribute("src", await uploadBase64Img(img.getAttribute("src")));
img.classList.remove("loading");
}
});
async function uploadBase64Img(base64Str) {
if (typeof base64Str !== 'string' || base64Str.length < 100) {
return base64Str;
}
const url = await b64ToUrl(base64); // your upload logic
return url;
} It inserts the base64 then updates the document with a url when it's uploaded. If you're doing live-syncing of documents you'll probably want to not sync base64 images and wait for the url update |
@ISNIT0 Can you provide a full example, please? It will help a beginner like myself a lot! |
@IJassar you mean the upload logic? This will vary completely based on your setup. I'm afraid I can't provide any more than this. |
@ISNIT0 I was thinking more like using imgur or some other API rather than a server. But thank you very much. |
Hi, on a angular project, I managed to get something working... the image is displayed with a blob uri only while in edit mode... On the server when I receive the Delta, I replace each custom blot entry url with real urls Hope it helps! |
@IJassar I adapted the approach shared by @ISNIT0 (I like the simplicity of it and the fact that you can see the image immediately in the editor). I used my internal upload server. Here's the full code. You can probably replace path in the // watch for images added:
quill.on("text-change", async function(delta, oldDelta, source) {
const imgs = Array.from(
quill.container.querySelectorAll('img[src^="data:"]:not(.loading)')
);
for (const img of imgs) {
img.classList.add("loading");
img.setAttribute("src", await uploadBase64Img(img.getAttribute("src")));
img.classList.remove("loading");
}
});
// wait for upload
async function uploadBase64Img(base64Str) {
if (typeof base64Str !== 'string' || base64Str.length < 100) {
return base64Str;
}
const url = await b64ToUrl(base64Str);
return url;
}
/**
* Convert a base64 string in a Blob according to the data and contentType.
*
* @param b64Data {String} Pure base64 string without contentType
* @param contentType {String} the content type of the file i.e (image/jpeg - image/png - text/plain)
* @param sliceSize {Int} SliceSize to process the byteCharacters
* @see http://stackoverflow.com/questions/16245767/creating-a-blob-from-a-base64-string-in-javascript
* @return Blob
*/
function b64toBlob(b64Data, contentType, sliceSize) {
contentType = contentType || '';
sliceSize = sliceSize || 512;
var byteCharacters = atob(b64Data);
var byteArrays = [];
for (var offset = 0; offset < byteCharacters.length; offset += sliceSize) {
var slice = byteCharacters.slice(offset, offset + sliceSize);
var byteNumbers = new Array(slice.length);
for (var i = 0; i < slice.length; i++) {
byteNumbers[i] = slice.charCodeAt(i);
}
var byteArray = new Uint8Array(byteNumbers);
byteArrays.push(byteArray);
}
var blob = new Blob(byteArrays, {type: contentType});
return blob;
}
// this is my upload function. I'm converting the base64 to blob for more efficient file
// upload and so it works with my existing file upload processing
// see here for more info on this approach https://ourcodeworld.com/articles/read/322/how-to-convert-a-base64-image-into-a-image-file-and-upload-it-with-an-asynchronous-form-using-jquery
function b64ToUrl(base64) {
return new Promise(resolve => {
// Split the base64 string in data and contentType
var block = base64.split(";");
// Get the content type of the image
var contentType = block[0].split(":")[1];
// get the real base64 content of the file
var realData = block[1].split(",")[1];
// Convert it to a blob to upload
var blob = b64toBlob(realData, contentType);
// create form data
const fd = new FormData();
// replace "file_upload" with whatever form field you expect the file to be uploaded to
fd.append('file_upload', blob);
const xhr = new XMLHttpRequest();
// replace "/upload" with whatever the path is to your upload handler
xhr.open('POST', '/upload', true);
xhr.onload = () => {
if (xhr.status === 200) {
// my upload handler responds with JSON of { "path": "/images/static_images/blob2.png" }
const url = JSON.parse(xhr.responseText).path;
resolve(url);
}
};
xhr.send(fd);
});
} |
@petergerard Thank you so very much! Oh man I have a lot of learning to do ^_^ |
I made an attempt in Vue to get the File instance instead of transforming base64 into blob by rewriting the image handler. First add in the component a hidden file input <template>
...
<quill-editor
id="content-editor"
ref="quillEditor"
v-model="content"
:options="qOptions"
/>
...
<input ref="imageInput" class="d-none" type="file" accept="image/*" @change="_doImageUpload">
...
</template> Then set data, computed, and methods like this data () {
return {
content: ''
qOptions: {
modules: {
toolbar: {
container: [['image']],
handlers: {
image: this.insertImage
}
}
},
theme: 'snow'
},
imageUpload: {
url: 'path/to/image/upload/endpoint'
}
}
},
computed: {
quillInstance () {
return this.$refs.quillEditor.quill
}
},
methods: {
insertImage () {
// manipulate the DOM to do a click on hidden input
this.$refs.imageInput.click()
},
async _doImageUpload (event) {
// for simplicity I only upload the first image
const file = event.target.files[0]
// create form data
const fd = new FormData()
// just add file instance to form data normally
fd.append('image', file)
// I use axios here, should be obvious enough
const response = await this.$axios.post(this.imageUpload.url, fd)
// clear input value to make selecting the same image work
event.target.value = ''
// get current index of the cursor
const currentIndex = this.quillInstance.selection.lastRange.index
// insert uploaded image url to 'image' embed (quill does this for you)
// the embed looks like this: <img src="{url}" />
this.quillInstance.insertEmbed(currentIndex, 'image', response.data.url)
// set cursor position to after the image
this.quillInstance.setSelection(currentIndex + 1, 0)
}
} Oh and I use vue-quill-editor with nuxt ssr for this. |
@fylzero my man! This was super helpful dude thank you!!! Just curious - how do you handle image deletions? i.e., if the user deletes the embedded image from Quill content, does the related image file somehow get deleted or just persist in the file storage? |
@congatw-cg It's funny you ask because I honestly never bothered tackling garbage clean up on this. I've thought about it but for the most part am assuming that most of the time if a user uploads an image they are actually using the image. I know it's not perfect but the only way i could think to clean up on this would be to delete the image when it is removed from the editor, but then you'd run into the problem of not allowing the user to ctrl/cmd+z and undo the image deletion. Kind of becomes a pain. So I just made the decision not to worry about it. |
You can use a cron for that. Maybe daily or so. What I do is i upload to some tmp folder and run the cron to delete after say 24hrs everyday. When you save the form (final upload), it just moves the file from the tmp storage to the permanent storage. Now the only limitation will be what if the cron is running when I have just uploaded an image. I think you can create back the image from the base64. You will just have to check if it exists or not to do this. Hope it helps. |
If setting up a cron job, you could use find to first get files older than 1 day, then pass these to rm:
`find /path/to/files* -mtime +1 -exec rm {} \;`
… On 4 Oct 2020, at 01:24, Mua Rachmann ***@***.***> wrote:
@fylzero <https://github.com/fylzero> my man! This was super helpful dude thank you!!! Just curious - how do you handle image deletions? i.e., if the user deletes the embedded image from Quill content, does the related image file somehow get deleted or just persist in the file storage?
You can use a cron for that. Maybe daily or so. What I do is i upload to some tmp folder and run the cron to delete after say 24hrs everyday. When you save the form (final upload), it just moves the file from the tmp storage to the permanent storage. Now the only limitation will be what if the cron is running when I have just uploaded an image. I think you can create back the image from the base64. You will just have to check if it exists or not to do this. Hope it helps.
—
You are receiving this because you commented.
Reply to this email directly, view it on GitHub <#1089 (comment)>, or unsubscribe <https://github.com/notifications/unsubscribe-auth/AB5IVPQ7TOEPS5QPKF6E643SI66FFANCNFSM4CUQGN3A>.
|
Was looking for a vue-only solution, and stumbled across this. You're a life-saver, man! Thanks a lot. |
I'm currently uploading images from the quill editor to my server. Basically I had to make changes in 2 places. Here's what I did Image upload button (edited quill.min,js): line 7045 if (fileInput.files != null && fileInput.files[0] != null) {
var reader = new FileReader();
reader.onload = function(e) {
const exec = async _ => {
// send the image as a file type to the server
async function fetchUrl(file) {
const formData = new FormData(form);
formData.append("image", file);
const response = await fetch('localhost/application/upload-image', { method: "POST", body: formData });
const json = await response.text();
return json;
}
var range = _this3.quill.getSelection(true);
const target = e.target.result;
const file = dataURLtoFile(target, 'file');
const json = await fetchUrl(file);
let obj = JSON.parse(json);
// server returns the uploaded url of the image
if (obj.url) {
let url = obj.url;
_this3.quill.updateContents(new _quillDelta2.default().retain(range.index).delete(range.length).insert({ image: url }), _emitter2.default.sources.USER);
} else {
alert("Error Occured");
}
_this3.quill.setSelection(range.index + 1, _emitter2.default.sources.SILENT);
fileInput.value = "";
}
exec();
};
reader.readAsDataURL(fileInput.files[0]);
} Pasting on the quill editor: let delay = ms => new Promise(res => setTimeout(res, ms));
// upload image by remote url
async function fetchUrl(src) {
const formData = newFormdata();
formData.append("src", src);
const response = await fetch(`localhost/app/upload-image`, {
method: "POST",
body: formData
});
const json = await response.text();
return json;
}
// On content paste with images
// Send a request containing source URL of the image, which the server downloads from
document.querySelector('.ql-editor').addEventListener('paste', e => {
const clipboardData = e.clipboardData || window.clipboardData;
let tmp = document.createElement('div');
tmp.innerHTML = clipboardData.getData('text/html');
const uploadCount = tmp.querySelectorAll("img").length;
const main = async () => {
// store the url format for your images so you upload only the images which have not been uploaded yet
const validateUrl = document.querySelector("[name='img_valid_url']").value;
await delay(1000); // wait for paste to finish
document.querySelectorAll('.ql-editor img').forEach(img => {
if ((img.src).indexOf(validateUrl) !== 0) img.classList.add("loading-img"); // show a loading image animation
})
const els = document.querySelectorAll('.ql-editor img');
async function loop() {
for (let x = 0; x < els.length; x++) {
let img = els[x]
let src = img.src;
img.classList.add("loading-img");
if (src.indexOf(validateUrl) !== 0) {
await delay(1000)
const upload = async () => {
const json = await fetchUrl(src);
let obj = JSON.parse(json);
if (obj.url) {
img.src = obj.url;
} else {
img.src = `${URL}/assets/img-not-found.png`;;
}
}
upload().then(a => { img.classList.remove("loading-img"); });
} else {
img.classList.remove("loading-img");
}
}
}
loop();
}
main();
}); Server side: $img = $_FILES['image'] ?? $_POST['src'] ?? false;
// create a function which saves the image either passed as file or saves it from a URL
if($img) {
$url = $this->imageModel->uploadImage($img);
if($url !== false) echo $url;
} I'm using cloudinary cloud for storing images so it can upload images either by their remote URL or the file itself |
To upload base64 image to a folder and url in database using quill editor via simple js and php My Javascript
Now My PHP
and after uploading the image in qpics folder, just insert the html in db |
My working solution only if user hit the submit button upload image to the server. var tempImage = [];
// Check whether quill content is empty
function isQuillEmpty(quill) {
if ((quill.getContents()['ops'] || []).length !== 1) {
return false
}
return quill.getText().trim().length === 0
}
// Delta to HTML
function deltaToHTML(delta) {
var tempQuill = new Quill(document.createElement('div'));
tempQuill.setContents(delta);
return tempQuill.root.innerHTML;
}
// Copy sanitize from link.js
function sanitize(url, protocols) {
var anchor = document.createElement('a');
anchor.href = url;
var protocol = anchor.href.slice(0, anchor.href.indexOf(':'));
return protocols.indexOf(protocol) > -1;
}
// do upload
async function uploadToServer(imageBlob) {
var imageToUpload = tempImage.find(item => item.blob === imageBlob);
var formData = new FormData();
formData.append('image', imageToUpload.file);
var res = await axios({
url: '/s3',
method: 'POST',
data: formData,
headers: {
'Content-Type': 'multipart/form-data'
}
});
return res.data;
}
// import existing image class
var ParchmentImage = Quill.import('formats/image');
// Overwrite static sanitize from image class
// data base64 too long, use blob instead (only for preview)
class KlsejournalImage extends ParchmentImage {
static sanitize(url) {
return sanitize(url, ['http', 'https', 'data', 'blob']) ? url : '//:0';
}
}
// Append blob & save local file
function imageHandler() {
var input = document.createElement('input');
input.setAttribute('type', 'file');
input.setAttribute('accept', 'image/*');
input.click();
input.onchange = () => {
var [file] = input.files;
if (/^image\//.test(file.type)) {
var range = this.quill.getSelection();
var blob = URL.createObjectURL(file);
this.quill.insertEmbed(range.index, 'image', blob);
this.quill.setSelection(range.index + 1);
tempImage.push({ blob, file });
} else {
alert('You could only upload images.');
}
}
}
// Changing the level to error.
Quill.debug('error');
// Register the new image class
Quill.register(KlsejournalImage);
// Initialize Quill editor
var editor = new Quill('#editor', {
placeholder: 'What do you want to talk about?',
theme: "snow",
modules: {
toolbar: {
container: [
["link", "image"],
],
handlers: {
image: imageHandler
}
},
}
});
// submit post
var btnPost = document.getElementById('submit-post')
btnPost.addEventListener('click', async function(e) {
if (isQuillEmpty(editor)) {
alert('Cannot submit empty post!');
return false;
}
var delta = editor.getContents();
for (var i = 0; i < delta.ops.length; i++) {
var insert = delta.ops[i].insert;
var has = Object.prototype.hasOwnProperty;
if (has.call(insert, 'image')) {
var imageUrl = await uploadToServer(insert.image);
insert.image = imageUrl;
}
}
var html = deltaToHTML(delta);
axios.post('/posts', { content: html })
.then((res) => {
window.location.reload();
});
}); |
@petergerard Thank you so much. Your sample code save me a lot of time! And the inline comments are really good. |
It's great but how can we get the images after uploading them, in the same order we putted them |
I leave this example that works perfectly for me. Good luck ;) |
Is anybody aware of how to add headers to the request when quill wants to retrieve an image from the backend? |
Quill 2.0 has been released (announcement post) with many changes and fixes. If this is still an issue please create a new issue after reviewing our updated Contributing guide 🙏 |
You can use quill-react-commercial. |
Quill works well, and inserting Base64-encoded images is ok but too larger images will exceed my database table limit.
if image insertion could be conducted by uploading to server folder (e.g. /img/test.jpg) and then include the url in the , that would make me not worry about limit, since texts don't occupy too much storage in database.
Is there any way to configure the quill to make this happen?
The text was updated successfully, but these errors were encountered: