quill
quill copied to clipboard
How to insert images by uploading to the server instead of Base64 encoding the images?
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?
Related: https://github.com/quilljs/quill/issues/90 https://github.com/quilljs/quill/pull/995
@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 quill.min.js
rather than core.js
that @jackmu95 altered. So, I am not sure how to deal with this issue, any other solution?
</style>
<link href="templates/texteditor/quill/quill.snow.css" rel="stylesheet">
<link href="templates/texteditor/quill/katex.min.css" rel="stylesheet">
<link href="templates/texteditor/quill/syntax-styles/googlecode.css" rel="stylesheet">
<!-- Include the Quill library -->
<script src="templates/texteditor/quill/katex.min.js"></script>
<script src="templates/texteditor/quill/highlight.pack.js"></script>
<script src="templates/texteditor/quill/quill.min.js"></script>
<script type="text/javascript">
hljs.initHighlightingOnLoad();
var quill = new Quill('#editor-container', {
modules: {
formula: true,
syntax: true,
toolbar: '#toolbar-container',
history:{
delay:2000,
maxStack:150,
userOnly: true
}
},
placeholder: 'Compose an epic...',
theme: 'snow'
});
</script>
@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.
http://codepen.io/jackmu95/pen/EgBKZr
@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 callback
function.
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
modules: {
toolbar: {
container: '#toolbar',
handlers: {
'image': imageHandler
}
}
},
the offical image handler is here:
function () {
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', () => {
if (fileInput.files != null && fileInput.files[0] != null) {
let reader = new FileReader();
reader.onload = (e) => {
let range = this.quill.getSelection(true);
this.quill.updateContents(new Delta()
.retain(range.index)
.delete(range.length)
.insert({ image: e.target.result })
, Emitter.sources.USER);
fileInput.value = "";
}
reader.readAsDataURL(fileInput.files[0]);
}
});
this.container.appendChild(fileInput);
}
fileInput.click();
}
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.
var formData = new FormData();
formData.append("image", fileInput.files[0]);
axios.post(UPLOAD_IMAGE_URI, formData, {
headers: {
'Content-Type': 'multipart/form-data'
},
responseType:'json'
})
.then(res => {
if(res.data.error == 0){
let range = quill.getSelection(true);
this.quill.updateContents(new Delta()
.retain(range.index)
.delete(range.length)
.insert({ image: res.data.url })
, Quill.sources.USER);
}else{
console.error(res.data);
}
})
.catch(e => {
console.error(e);
});
@magicdvd 3Q
@magicdvd
Why is there an error??
@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 fileInput
, axios
, and UPLOAD_IMAGE_URI
at the very least.
@lpp288 @gpyys 你们这个问题 都解决没 有没有什么好的办法
I solved the problem that upload image with url. This is my code, hope help you:
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.
var xhr = new XMLHttpRequest();
xhr.open('POST', url, true);
xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
xhr.upload.addEventListener("progress", function(e) {
var progress = Math.round((e.loaded * 100.0) / e.total);
document.getElementById('progress').style.width = progress + "%";
});
I have this working well, but quill.insertEmbed(range.index, 'image', url);
is inserting images inline... anyone know how I can change this so that the current paragraph is split with the image inserted in between?
I wrote a plugin to upload image: quill-plugin-image-upload
With this plugin we can:
- 🌟upload a image when it is inserted, and then replace the base64-url with a http-url
- 🌟preview the image which is uploading with a loading animation
- 🌟when the image is uploading, we can keep editing the content including changing the image's position or even delete the image.
And of course, it's easy to use! 😁
I solved the problem that upload image with url. This is my code, hope help you:
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(); });
this work well, thanks~ and the cursor should be moved after the pic you inserted, like this
...
function insertToEditor(url: string) {
// push image url to rich editor.
const range = editor.getSelection();
editor.insertEmbed(range.index, 'image', `http://localhost:9000${url}`);
editor.setSelection(range.index + 1);
}
...
I solved the problem that upload image with url. This is my code, hope help you:
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(); });
this work well, thanks~ and the cursor should be moved after the pic you inserted, like this
... function insertToEditor(url: string) { // push image url to rich editor. const range = editor.getSelection(); editor.insertEmbed(range.index, 'image', `http://localhost:9000${url}`); editor.setSelection(range.index + 1); } ...
Hey @clarissahu still it's not setting index next to image. Any other solution?
A little late but what about uploading the delta content to the server as is and then finding "data:image/png;base64,...", decode the base64 to an image and then replace with the image URL?
@magicdvd I tried this and while I almost got it working, I got an error saying Delta is not defined, so I tried importing by doing
var Delta = Quill.import('delta');
and that fixed the delta undefined error, but then I got
Uncaught ReferenceError: Emitter is not defined
and I can't import emitter from quill and I can't seem to find a solution to this problem.
Any tips?
I want format image when insert image, like this:
class ImageBlot extends BlockEmbed {
static create(src) {
const node = super.create()
node.setAttribute('src', src)
return node
}
static value(node) {
return node.getAttribute('src')
}
static formats(node) {
// We still need to report unregistered embed formats
let format = {}
format.srcset = node.getAttribute('src') + ' 2x'
return format
}
format(name, value) {
// Handle unregistered embed formats
if (name === 'srcset') {
if (value) {
this.domNode.setAttribute(name, value)
} else {
this.domNode.removeAttribute(name, value)
}
} else {
super.format(name, value)
}
}
}
ImageBlot.blotName = 'imageBlot'
ImageBlot.tagName = 'img'
class ImageUpload {
constructor(quill, options = {}) {
// save the quill reference
this.quill = quill;
// save options
this.options = options;
// listen for drop and paste events
this.quill.root.addEventListener('drop', ev => {
ev.preventDefault()
let native
if (document.caretRangeFromPoint) {
native = document.caretRangeFromPoint(ev.clientX, ev.clientY);
} else if (document.caretPositionFromPoint) {
const position = document.caretPositionFromPoint(ev.clientX, ev.clientY);
native = document.createRange();
native.setStart(position.offsetNode, position.offset);
native.setEnd(position.offsetNode, position.offset);
} else {
return;
}
const normalized = quill.selection.normalizeNative(native);
const range = quill.selection.normalizedToRange(normalized);
if (ev.dataTransfer.files.length !== 0) this.upload(range, Array.from(ev.dataTransfer.files))
})
}
upload(range, files) {
files.forEach(file => {
const observable = qiniu.upload(file, `md/${uuidv4()}.${file.name.split('.').pop()}`, token)
observable.subscribe({ complete: ({ key, hash, domain }) => this.insertToEditor(range, `${domain}/${key}`), error: (e) => console.error(e) })
})
}
insertToEditor(range, src) {
this.quill.container.focus()
this.quill.selection.update(Quill.sources.SILENT)
setTimeout(() => {
const update = new Delta().retain(range.index).delete(range.length).insert({imageBlot: src})
this.quill.updateContents(update, Quill.sources.USER)
this.quill.setSelection(
range.index + 1,
Quill.sources.SILENT
)
}, 1)
}
}
what should I do
Iam using these configurations but cant add imagehandling
this.editorForm = new FormGroup({
'editor': new FormControl(null)
})
config = {
toolbar: {
container:
[
[{ 'placeholder': ['[GuestName]', '[HotelName]'] }], // my custom dropdown
['bold', 'italic', 'underline', 'strike'], // toggled buttons
['blockquote', 'code-block'],
[{ 'header': 1 }, { 'header': 2 }], // custom button values
[{ 'list': 'ordered' }, { 'list': 'bullet' }],
[{ 'script': 'sub' }, { 'script': 'super' }], // superscript/subscript
[{ 'indent': '-1' }, { 'indent': '+1' }], // outdent/indent
[{ 'direction': 'rtl' }], // text direction
[{ 'size': ['small', false, 'large', 'huge'] }], // custom dropdown
[{ 'header': [1, 2, 3, 4, 5, 6, false] }],
[{ 'color': [] }, { 'background': [] }], // dropdown with defaults from theme
[{ 'font': [] }],
[{ 'align': [] }],
['clean'] // remove formatting button
],
handlers: {
}
}
}