I am currently using the React-Quill editor and the functionality for image upload is achieved through the 'formats/image' feature provided by Quill.
Image resize option doesn't included in the react-quill editor by default. Consequently, we have incorporated the quill-blot-formatter package and registered it with the Quill modules to enable this feature.
Quill.register('modules/blotFormatter', BlotFormatter);
The image resize feature works fine but it prohibits the scrolling effect of the Quill editor if the mouse focus is inside the resize window
Image resize prohibits editor scrolling
If the mouse pointer is outside of the image resize area, scrolling functions properly. Hence some of the listener from the ql-blot-formatter stopsPropagation of the mouse events to the react-quill(the event is not bubbling up to the ql-editor).
Could someone kindly assist me in resolving this issue.?
Below is the code snippet that I've implemented for the reference.
QuillEditor.js
import ReactQuill from 'react-quill';
const Quill = ReactQuill.Quill;
const Embed = Quill.import('blots/embed');
import BlotFormatter from 'quill-blot-formatter/dist/BlotFormatter';
import { CustomImage } from './CustomPlugins/CustomImage';
//to register uploaded image as blot
Quill.register(CustomImage, true);
// Register image resize
Quill.register('modules/blotFormatter', BlotFormatter);
<ReactQuill
ref={editorRef}
className={`w-full ${height} ${className} `}
formats={formats}
id={id}
modules={modules(toolbarId)}
theme="snow"
value={state}
onChange={handleChange}
/>
function modules(toolbarId) {
return {
blotFormatter: {
specs: [CustomImageSpec], //register the custom spec
align: {
icons: alignmentStyles,
},
},
};
}
CustomImageSpec.js
import DeleteIcon from '../../../assets/DeleteIcon.svg';
import { alignToolBar } from './helper';
import ImageSpec from 'quill-blot-formatter/dist/specs/ImageSpec';
// Custom image spec for blot formatter
// Adding delete icon to the blot toolbar on mount phase
export class CustomImageSpec extends ImageSpec {
img;
deleteIcon;
constructor(formatter) {
super(formatter);
this.img = null;
this.deleteIcon = this.createDeleteIcon();
}
createDeleteIcon() {
const spanElement = document.createElement('span');
const imgElement = document.createElement('img');
imgElement.src = DeleteIcon;
imgElement.alt = 'test image';
const clsList = ['blot-formatter__toolbar-button', 'blot-delete-icon'];
spanElement.classList.add(...clsList);
spanElement.appendChild(imgElement);
spanElement.style.cssText =
'display: flex; align-items:center; justify-content:center; width: 24px; height: 24px; cursor:pointer; user-select:none; padding: 2px';
spanElement.addEventListener('click', this.onDeleteIconClick);
return spanElement;
}
init() {
this.formatter.quill.root.addEventListener('click', this.onClick);
this.formatter.quill.root.addEventListener('scroll', (e) => {
this.formatter.repositionOverlay();
});
}
getTargetElement() {
return this.img;
}
onDeleteIconClick = () => {
// Handle delete icon click
if (this.img) {
this.img.remove();
this.formatter.hide();
}
};
onHide() {
this.img = null;
}
onClick = (event) => {
const el = event.target;
if (!el || el.tagName !== 'IMG') {
return;
}
this.img = el;
this.formatter.show(this);
alignToolBar(this.formatter);
this.formatter.overlay
?.getElementsByClassName('blot-formatter__toolbar')[0]
?.appendChild(this.deleteIcon);
};
}
CustomImage.js
import { Quill } from 'react-quill';
// eslint-disable-next-line react-refresh/only-export-components
const BaseImage = Quill.import('formats/image');
// const BaseImage = Quill.import('blots/embed')
// eslint-disable-next-line react-refresh/only-export-components
const ATTRIBUTES = ['alt', 'height', 'width', 'style'];
export class CustomImage extends BaseImage {
static formats(domNode) {
return ATTRIBUTES.reduce(function (formats, attribute) {
if (domNode.hasAttribute(attribute)) {
formats[attribute] = domNode.getAttribute(attribute);
}
return formats;
}, {});
}
format(name, value) {
if (ATTRIBUTES?.indexOf(name) > -1) {
if (value) {
this.domNode.setAttribute(name, value);
} else {
this.domNode.removeAttribute(name);
}
} else {
super.format(name, value);
}
}
}
//to register uploaded image as blot
Quill.register(CustomImage, true);