quill
quill copied to clipboard
How to insert editable images and videos?
Please describe the a concise description and fill out the details below. It will help others efficiently understand your request and get to an answer instead of repeated back and forth. Providing a minimal, complete and verifiable example will further increase your chances that someone can help.
Steps for Reproduction
- Insert an image
- Try to update its details
- Fail
Expected behavior:
- insert an image
- click on it to change details
- Get some kind of form to update details (preferably a custom callback)
- Save
- See updated image in editor
Actual behavior: You can;t edit images once placed.
Platforms:
All
Include browser, operating system and respective versions
Version: 2.0 - Develop branch
Run Quill.version
to find out
All that out of the way...
I am trying to create a user-friendly WYSIWYG editor for my CMS. Of all the options - and I have tried way too many to be still called sane - Quill is the closest I can get to working. And I'm still failing.
All I really need outside of the standard functionality is the ability to use my custom asset / image picker for images and videos and to use my custom link picker for link URLs - the rest can be stock Quill.
I got to the point where I am attaching my custom image callback to any images inside the editor container so that clicking calls it up - awesome. However, I am currently editing the HTMLElement for the image directly. That seems to really mess the editor up, leaving images that can;t be deleted in the editor, which is a terrible user experience. I suspect that's because I'm updating the elemnt and not its Parchment blot or whatever, but I can not seem to make heads or tails of this thing - how can I update a BlockEmbed blot's attributes and have that reflected int he editor? Do I need to delete the old blot and replace with it a new one with updated attributes? If so, how the hell do I do that?
Here's the code I use to set up the editor:
let BlockEmbed = Editor.import('blots/block/embed');
class ImageBlot extends BlockEmbed {
static create(value) {
let node = super.create();
node.setAttribute('alt', value.alt);
node.setAttribute('src', value.src);
node.setAttribute('width', value.width);
node.setAttribute('height', value.height);
return node;
}
static value(node) {
return {
alt: node.getAttribute('alt'),
src: node.getAttribute('src'),
width: node.getAttribute('width'),
height: node.getAttribute('height')
};
}
}
ImageBlot.blotName = 'image';
ImageBlot.tagName = 'img';
Editor.register(ImageBlot);
var editor = new Editor(el, {
modules: {
toolbar: toolbarOptions
},
theme: 'snow'
})
var editBox = editor.root
var images = editBox.getElementsByTagName('img')
for(let n = 0; n < images.length; n++) {
images[n].addEventListener('click', (event) => { imgHandler(false, event.currentTarget, editor)})
}
var toolbar = editor.getModule("toolbar");
toolbar.addHandler("image", (i, cb) => { imgHandler(i, cb, editor) });
return editor;
Here's my imgHandler - all it really does is grab the currently selected element or location and open a form that allows a user to select an image from a list, which then uses a callback to populate the image with those attributes.
var index;
var imgElement = '';
var imgObj = {}
if(typeof value == 'string') {
console.log("It's a string!")
//We can assume the value is an href; put that in the image form fields
imgObj["href"] = value
} else if(value) {
if(value.nodeType) {
let quillImg = Editor.find(value);
currImage = value
imgObj.width = ((value.getAttribute("width") !== null && value.getAttribute("width") != 'null') ? value.getAttribute("width") : '')
imgObj.height = ((value.getAttribute("height") !== null && value.getAttribute("height") != 'null') ? value.getAttribute("height") : '')
imgObj.alt = ((value.getAttribute("alt") !== null && value.getAttribute("alt") != 'null') ? value.getAttribute("alt") : '')
imgObj.src = ((value.getAttribute("src") !== null && value.getAttribute("src") != 'null') ? value.getAttribute("src") : '')
} else if(value.href) {
console.log("It's an object!")
//It's an object in the format {"href": link_url, "alt": alt_text, "width": width, "height": height}
//TODO something
//Put the values in the object into the image form for editing
} else {
console.log("It's... " + typeof value)
//Doing nothing for now
}
} else {
console.log("Value is " + value + ": " + typeof value)
//Nothing to do here
}
document.getElementById("image_width").value = (imgObj.width && imgObj.width !== null ? imgObj.width : '');
document.getElementById("image_height").value = (imgObj.height && imgObj.height !== null ? imgObj.height : '');
document.getElementById("image_alt").value = (imgObj.alt && imgObj.alt !== null ? imgObj.alt : '');
document.getElementById("src").value = (imgObj.src && imgObj.src !== null ? imgObj.src : '');
And here's the callback that's actually trying to embed the image in the editor:
let range = currEditor.getSelection(true);
if(currImage !== null) {
/*
//This is what I *was* doing - it caused images to not be able to be deleted from the editor.
if(imageObj.src && imageObj.src != '') {
currImage.setAttribute("src", imageObj.src)
}
if(imageObj.src && imageObj.src != '') {
currImage.setAttribute("width", imageObj.width)
}
if(imageObj.src && imageObj.src != '') {
currImage.setAttribute("height", imageObj.height)
}
if(imageObj.src && imageObj.src != '') {
currImage.setAttribute("alt", imageObj.alt)
}
*/
var quillImg = Editor.find(currImage)
quillImg.format('image', imageObj)
currImage = null;
} else {
currEditor.insertText(range.index, '\n', Editor.sources.USER);
currEditor.insertEmbed(range.index + 1, 'image', imageObj, Editor.sources.USER);
}
currEditor.setSelection(range.index + 2, Editor.sources.SILENT);
currEditor = null
currImage = null
var modal = document.getElementById("image_picker_modal")
modal.close()
Help?
I had been using quill-image-resize for a while but wanted to add rotation as well so I modified that package a bit.. can't remember exactly where I modified it.. I've been trying to make vuequill work with quill 2 so i've got a lot of packages taken offline since they don't seem to be maintained any more..
Modified from this one: https://github.com/kensnyder/quill-image-resize-module/tree/master/src
I have the alignment functions commented out in the Toolbar.js file since I never used those.. I also added in the bit that adds 1 pixel to the image.width on the two rotate functions since rotations weren't writing out unless I resized the image afterwards or clicked outsize and added a space or changed some text.. modifying the width seemed to make it save the rotation properly..
It's a hack but it works for me at the moment.. :) A lot of quill modules don't seem to be very active.
ImageResize.js
import defaultsDeep from "lodash/defaultsDeep";
import DefaultOptions from "./DefaultOptions";
import { DisplaySize } from "./modules/DisplaySize";
import { Toolbar } from "./modules/Toolbar";
import { Resize } from "./modules/Resize";
const knownModules = { DisplaySize, Toolbar, Resize };
/**
* Custom module for quilljs to allow user to resize <img> elements
* (Works on Chrome, Edge, Safari and replaces Firefox's native resize behavior)
* @see https://quilljs.com/blog/building-a-custom-module/
*/
export default class ImageResize {
constructor(quill, options = {}) {
// save the quill reference and options
this.quill = quill;
// Apply the options to our defaults, and stash them for later
// defaultsDeep doesn't do arrays as you'd expect, so we'll need to apply the classes array from options separately
let moduleClasses = false;
if (options.modules) {
moduleClasses = options.modules.slice();
}
// Apply options to default options
this.options = defaultsDeep({}, options, DefaultOptions);
// (see above about moduleClasses)
if (moduleClasses !== false) {
this.options.modules = moduleClasses;
}
// disable native image resizing on firefox
document.execCommand("enableObjectResizing", false, "false");
// respond to clicks inside the editor
this.quill.root.addEventListener("click", this.handleClick, false);
this.quill.root.parentNode.style.position =
this.quill.root.parentNode.style.position || "relative";
// setup modules
this.moduleClasses = this.options.modules;
this.modules = [];
}
initializeModules = () => {
this.removeModules();
this.modules = this.moduleClasses.map(
(ModuleClass) => new (knownModules[ModuleClass] || ModuleClass)(this)
);
this.modules.forEach((module) => {
module.onCreate();
});
this.onUpdate();
};
onUpdate = () => {
this.repositionElements();
this.modules.forEach((module) => {
module.onUpdate();
});
};
removeModules = () => {
this.modules.forEach((module) => {
module.onDestroy();
});
this.modules = [];
};
handleClick = (evt) => {
if (
evt.target &&
evt.target.tagName &&
evt.target.tagName.toUpperCase() === "IMG"
) {
if (this.img === evt.target) {
// we are already focused on this image
return;
}
if (this.img) {
// we were just focused on another image
this.hide();
}
// clicked on an image inside the editor
this.show(evt.target);
} else if (this.img) {
// clicked on a non image
this.hide();
}
};
show = (img) => {
// keep track of this img element
this.img = img;
this.showOverlay();
this.initializeModules();
};
showOverlay = () => {
if (this.overlay) {
this.hideOverlay();
}
// BUG: scroll top when setSelection method is called
this.quill.setSelection(null);
// prevent spurious text selection
this.setUserSelect("none");
// listen for the image being deleted or moved
document.addEventListener("keyup", this.checkImage, true);
this.quill.root.addEventListener("input", this.checkImage, true);
// Create and add the overlay
this.overlay = document.createElement("div");
Object.assign(this.overlay.style, this.options.overlayStyles);
this.quill.root.parentNode.appendChild(this.overlay);
this.repositionElements();
};
hideOverlay = () => {
if (!this.overlay) {
return;
}
// Remove the overlay
this.quill.root.parentNode.removeChild(this.overlay);
this.overlay = undefined;
// stop listening for image deletion or movement
document.removeEventListener("keyup", this.checkImage);
this.quill.root.removeEventListener("input", this.checkImage);
// reset user-select
this.setUserSelect("");
};
repositionElements = () => {
if (!this.overlay || !this.img) {
return;
}
// position the overlay over the image
const parent = this.quill.root.parentNode;
const img = this.img;
const imgRect = this.img.getBoundingClientRect();
const containerRect = parent.getBoundingClientRect();
const imgStyle = this.getImageStyle(
img.height,
img.width,
imgRect.height,
imgRect.width
);
Object.assign(this.img.style, imgStyle);
setTimeout(() => {
const rotation = +img.getAttribute("_rotation") || 0;
const imgRect2 = img.getBoundingClientRect();
const overlayStyle = this.getOverlayStyle(
rotation,
img.width,
img.height,
imgRect2.left,
imgRect2.top,
containerRect.left,
containerRect.top,
parent.scrollLeft,
parent.scrollTop
);
console.log(overlayStyle);
Object.assign(this.overlay.style, overlayStyle);
}, 30);
// Object.assign(this.overlay.style, {
// left: `${imgRect.left - containerRect.left - 1 + parent.scrollLeft}px`,
// top: `${imgRect.top - containerRect.top + parent.scrollTop}px`,
// width: `${imgRect.width}px`,
// height: `${imgRect.height}px`,
// });
};
getImageStyle = (imgH, imgW, imgRectH, imgRectW) => {
const offsetX = (imgRectW - imgW) / 2;
const offsetY = (imgRectH - imgH) / 2;
const styles = {
margin: `${offsetY}px ${offsetX}px`,
};
return styles;
};
getOverlayStyle = (
rotation,
imgW,
imgH,
imgRectL,
imgRectT,
cRectL,
cRectT,
pScrollL,
pScrollT
) => {
const styles = {};
switch (rotation) {
case 90:
case 270:
styles.width = `${imgH}px`;
styles.height = `${imgW}px`;
styles.left = `${imgRectL - cRectL - 1 + pScrollL}px`;
styles.top = `${imgRectT - cRectT + pScrollT}px`;
break;
case 180:
case 0:
default:
styles.width = `${imgW}px`;
styles.height = `${imgH}px`;
styles.left = `${imgRectL - cRectL - 1 + pScrollL}px`;
styles.top = `${imgRectT - cRectT + pScrollT}px`;
}
return styles;
};
hide = () => {
this.hideOverlay();
this.removeModules();
this.img = undefined;
};
setUserSelect = (value) => {
["userSelect", "mozUserSelect", "webkitUserSelect", "msUserSelect"].forEach(
(prop) => {
// set on contenteditable element and <html>
this.quill.root.style[prop] = value;
document.documentElement.style[prop] = value;
}
);
};
checkImage = (evt) => {
if (this.img) {
if (evt.keyCode == 46 || evt.keyCode == 8) {
this.quill.find(this.img).deleteAt(0);
}
this.hide();
}
};
}
DefaultOptions.js
export default {
modules: ["DisplaySize", "Toolbar", "Resize"],
overlayStyles: {
position: "absolute",
boxSizing: "border-box",
border: "1px dashed #444",
},
handleStyles: {
position: "absolute",
height: "12px",
width: "12px",
backgroundColor: "white",
border: "1px solid #777",
boxSizing: "border-box",
opacity: "0.80",
},
displayStyles: {
position: "absolute",
font: "12px/1.0 Arial, Helvetica, sans-serif",
padding: "4px 8px",
textAlign: "center",
backgroundColor: "white",
color: "#333",
border: "1px solid #777",
boxSizing: "border-box",
opacity: "0.80",
cursor: "default",
},
toolbarStyles: {
position: "absolute",
top: "-12px",
right: "0",
left: "0",
height: "0",
minWidth: "100px",
font: "12px/1.0 Arial, Helvetica, sans-serif",
textAlign: "center",
color: "#333",
boxSizing: "border-box",
cursor: "default",
},
toolbarButtonStyles: {
display: "inline-block",
width: "24px",
height: "24px",
padding: "3px",
background: "white",
border: "1px solid #999",
verticalAlign: "middle",
},
toolbarButtonSvgStyles: {
fill: "#444",
stroke: "#444",
strokeWidth: "2",
},
};
modules/baseModule.js
export class BaseModule {
constructor(resizer) {
this.overlay = resizer.overlay;
this.img = resizer.img;
this.options = resizer.options;
this.requestUpdate = resizer.onUpdate;
}
/*
requestUpdate (passed in by the library during construction, above) can be used to let the library know that
you've changed something about the image that would require re-calculating the overlay (and all of its child
elements)
For example, if you add a margin to the element, you'll want to call this or else all the controls will be
misaligned on-screen.
*/
/*
onCreate will be called when the element is clicked on
If the module has any user controls, it should create any containers that it'll need here.
The overlay has absolute positioning, and will be automatically repositioned and resized as needed, so you can
use your own absolute positioning and the 'top', 'right', etc. styles to be positioned relative to the element
on-screen.
*/
onCreate = () => {};
/*
onDestroy will be called when the element is de-selected, or when this module otherwise needs to tidy up.
If you created any DOM elements in onCreate, please remove them from the DOM and destroy them here.
*/
onDestroy = () => {};
/*
onUpdate will be called any time that the element is changed (e.g. resized, aligned, etc.)
This frequently happens during resize dragging, so keep computations light while here to ensure a smooth
user experience.
*/
onUpdate = () => {};
}
DisplaySize.js (this is if you want the size of the image displayed on an overlay during resizing)
import { BaseModule } from './BaseModule';
export class DisplaySize extends BaseModule {
onCreate = () => {
// Create the container to hold the size display
this.display = document.createElement('div');
// Apply styles
Object.assign(this.display.style, this.options.displayStyles);
// Attach it
this.overlay.appendChild(this.display);
};
onDestroy = () => {};
onUpdate = () => {
if (!this.display || !this.img) {
return;
}
const size = this.getCurrentSize();
this.display.innerHTML = size.join(' × ');
if (size[0] > 120 && size[1] > 30) {
// position on top of image
Object.assign(this.display.style, {
right: '4px',
bottom: '4px',
left: 'auto',
});
}
else if (this.img.style.float == 'right') {
// position off bottom left
const dispRect = this.display.getBoundingClientRect();
Object.assign(this.display.style, {
right: 'auto',
bottom: `-${dispRect.height + 4}px`,
left: `-${dispRect.width + 4}px`,
});
}
else {
// position off bottom right
const dispRect = this.display.getBoundingClientRect();
Object.assign(this.display.style, {
right: `-${dispRect.width + 4}px`,
bottom: `-${dispRect.height + 4}px`,
left: 'auto',
});
}
};
getCurrentSize = () => [
this.img.width,
Math.round((this.img.width / this.img.naturalWidth) * this.img.naturalHeight),
];
}
Resize.js
import { BaseModule } from "./BaseModule";
export class Resize extends BaseModule {
onCreate = () => {
// track resize handles
this.boxes = [];
// add 4 resize handles
this.addBox("nwse-resize"); // top left
this.addBox("nesw-resize"); // top right
this.addBox("nwse-resize"); // bottom right
this.addBox("nesw-resize"); // bottom left
this.positionBoxes();
};
onDestroy = () => {
// reset drag handle cursors
this.setCursor("");
};
positionBoxes = () => {
const handleXOffset = `${
-parseFloat(this.options.handleStyles.width) / 2
}px`;
const handleYOffset = `${
-parseFloat(this.options.handleStyles.height) / 2
}px`;
// set the top and left for each drag handle
[
{ left: handleXOffset, top: handleYOffset }, // top left
{ right: handleXOffset, top: handleYOffset }, // top right
{ right: handleXOffset, bottom: handleYOffset }, // bottom right
{ left: handleXOffset, bottom: handleYOffset }, // bottom left
].forEach((pos, idx) => {
Object.assign(this.boxes[idx].style, pos);
});
};
addBox = (cursor) => {
// create div element for resize handle
const box = document.createElement("div");
// Star with the specified styles
Object.assign(box.style, this.options.handleStyles);
box.style.cursor = cursor;
// Set the width/height to use 'px'
box.style.width = `${this.options.handleStyles.width}px`;
box.style.height = `${this.options.handleStyles.height}px`;
// listen for mousedown on each box
box.addEventListener("touchstart", this.handleMousedown, {
passive: true,
});
box.addEventListener("mousedown", this.handleMousedown, { passive: true });
box.addEventListener("touchstart", this.handleMousedown, { passive: true });
// add drag handle to document
this.overlay.appendChild(box);
// keep track of drag handle
this.boxes.push(box);
};
handleMousedown = (evt) => {
// note which box
this.dragBox = evt.target;
// note starting mousedown position
if (evt.touches) {
// for mobile devices get clientX of first touch point
this.dragStartX = evt.touches[0].clientX;
} else {
this.dragStartX = evt.clientX;
}
// if (evt.type === 'mousedown') {
// this.dragStartX = evt.clientX
// } else {
// this.dragStartX = evt.touches[0].clientX
// }
// store the width before the drag
this.preDragWidth = this.img.width || this.img.naturalWidth;
// set the proper cursor everywhere
this.setCursor(this.dragBox.style.cursor);
// listen for movement and mouseup
document.addEventListener("touchmove", this.handleDrag, { pssive: true });
document.addEventListener("touchend", this.handleMouseup, { pssive: true });
document.addEventListener("mousemove", this.handleDrag, { pssive: true });
document.addEventListener("mouseup", this.handleMouseup, { pssive: true });
};
handleMouseup = () => {
// reset cursor everywhere
this.setCursor("");
// stop listening for movement and mouseup
document.removeEventListener("touchmove", this.handleDrag);
document.removeEventListener("touchend", this.handleMouseup);
document.removeEventListener("mousemove", this.handleDrag);
document.removeEventListener("mouseup", this.handleMouseup);
};
handleDrag = (evt) => {
if (!this.img) {
// image not set yet
return;
}
// update image size
let deltaX;
if (evt.touches) {
deltaX = evt.touches[0].clientX - this.dragStartX;
} else {
deltaX = evt.clientX - this.dragStartX;
}
// let deltaX
// if (evt.type === 'mousemove') {
// deltaX = evt.clientX - this.dragStartX
// } else {
// deltaX = evt.changedTouches[0].clientX - this.dragStartX
// }
if (this.dragBox === this.boxes[0] || this.dragBox === this.boxes[3]) {
// left-side resize handler; dragging right shrinks image
this.img.width = Math.round(this.preDragWidth - deltaX);
} else {
// right-side resize handler; dragging right enlarges image
this.img.width = Math.round(this.preDragWidth + deltaX);
}
this.requestUpdate();
};
setCursor = (value) => {
[document.body, this.img].forEach((el) => {
el.style.cursor = value; // eslint-disable-line no-param-reassign
});
};
}
Toolbar.js (I added the rotation functionality on here under the rotate-left and rotate-right
// import Parchment from "parchment";
import { Quill } from "@vueup/vue-quill";
const Parchment = Quill.import("parchment");
import { BaseModule } from "./BaseModule";
const FloatStyle = new Parchment.Attributor.Style("float", "float");
const MarginStyle = new Parchment.Attributor.Style("margin", "margin");
const DisplayStyle = new Parchment.Attributor.Style("display", "display");
const TransformStyle = new Parchment.Attributor.Style("transform", "transform");
// const IconAlignCenter = `<svg viewbox="0 0 18 18">
// <line class="ql-stroke" x1="15" x2="3" y1="9" y2="9"></line>
// <line class="ql-stroke" x1="14" x2="4" y1="14" y2="14"></line>
// <line class="ql-stroke" x1="12" x2="6" y1="4" y2="4"></line>
// </svg>`;
// const IconAlignRight = `<svg viewbox="0 0 18 18">
// <line class="ql-stroke" x1="15" x2="3" y1="9" y2="9"></line>
// <line class="ql-stroke" x1="15" x2="5" y1="14" y2="14"></line>
// <line class="ql-stroke" x1="15" x2="9" y1="4" y2="4"></line>
// </svg>`;
// const IconAlignLeft = `<svg viewbox="0 0 18 18">
// <line class="ql-stroke" x1="3" x2="15" y1="9" y2="9"></line>
// <line class="ql-stroke" x1="3" x2="13" y1="14" y2="14"></line>
// <line class="ql-stroke" x1="3" x2="9" y1="4" y2="4"></line>
// </svg>`;
const IconRedo = `<svg viewbox="0 0 18 18">
<polygon class="ql-fill ql-stroke" points="12 10 14 12 16 10 12 10"></polygon>
<path class="ql-stroke" d="M9.91,13.91A4.6,4.6,0,0,1,9,14a5,5,0,1,1,5-5"></path>
</svg>`;
const IconUndo = `<svg viewbox="0 0 18 18">
<polygon class="ql-fill ql-stroke" points="6 10 4 12 2 10 6 10"></polygon>
<path class="ql-stroke" d="M8.09,13.91A4.6,4.6,0,0,0,9,14,5,5,0,1,0,4,9"></path>
</svg>`;
export class Toolbar extends BaseModule {
rotation = 0;
onCreate = () => {
// Setup Toolbar
this.toolbar = document.createElement("div");
Object.assign(this.toolbar.style, this.options.toolbarStyles);
this.overlay.appendChild(this.toolbar);
// Setup Buttons
this._defineAlignments();
this._addToolbarButtons();
this.rotation = +this.img.getAttribute("_rotation") || 0;
};
// The toolbar and its children will be destroyed when the overlay is removed
onDestroy = () => {};
// Nothing to update on drag because we are are positioned relative to the overlay
onUpdate = () => {};
_defineAlignments = () => {
this.alignments = [
// {
// icon: IconAlignLeft,
// apply: () => {
// DisplayStyle.add(this.img, "inline");
// FloatStyle.add(this.img, "left");
// MarginStyle.add(this.img, "0 1em 1em 0");
// },
// isApplied: () => FloatStyle.value(this.img) == "left",
// },
// {
// icon: IconAlignCenter,
// apply: () => {
// DisplayStyle.add(this.img, "block");
// FloatStyle.remove(this.img);
// MarginStyle.add(this.img, "auto");
// },
// isApplied: () => MarginStyle.value(this.img) == "auto",
// },
// {
// icon: IconAlignRight,
// apply: () => {
// DisplayStyle.add(this.img, "inline");
// FloatStyle.add(this.img, "right");
// MarginStyle.add(this.img, "0 0 1em 1em");
// },
// isApplied: () => FloatStyle.value(this.img) == "right",
// },
{
name: "rotate-left",
icon: IconUndo,
apply: () => {
const rotationvalue = this._setRotation("left");
// console.log("Rotate left!", { rotationvalue });
// console.log(this.img);
this.img.setAttribute("_rotation", this.rotation);
TransformStyle.add(this.img, rotationvalue);
this.img.width = this.img.width + 1;
// this.requestUpdate();
},
isApplied: () => {},
},
{
name: "rotate-right",
icon: IconRedo,
apply: () => {
const rotationvalue = this._setRotation("right");
// console.log("Rotate right!", { rotationvalue });
this.img.setAttribute("_rotation", this.rotation);
TransformStyle.add(this.img, rotationvalue);
this.img.width = this.img.width + 1;
// this.requestUpdate();
},
isApplied: () => {},
},
];
};
_addToolbarButtons = () => {
const buttons = [];
this.alignments.forEach((alignment, idx) => {
const button = document.createElement("span");
buttons.push(button);
button.innerHTML = alignment.icon;
button.addEventListener("click", () => {
// deselect all buttons
buttons.forEach((button) => (button.style.filter = ""));
if (alignment.isApplied()) {
// If applied, unapply
FloatStyle.remove(this.img);
MarginStyle.remove(this.img);
DisplayStyle.remove(this.img);
} else {
// otherwise, select button and apply
this._selectButton(button);
alignment.apply();
}
// image may change position; redraw drag handles
this.requestUpdate();
});
Object.assign(button.style, this.options.toolbarButtonStyles);
if (idx > 0) {
button.style.borderLeftWidth = "0";
}
Object.assign(
button.children[0].style,
this.options.toolbarButtonSvgStyles
);
if (alignment.isApplied()) {
// select button if previously applied
this._selectButton(button);
}
this.toolbar.appendChild(button);
});
};
_selectButton = (button) => {
button.style.filter = "invert(20%)";
};
_setRotation = (direction) => {
const oldRotation = this.rotation;
const increment = direction == "left" ? -90 : 90;
this.rotation = (oldRotation + 360 + increment) % 360;
return "rotate(" + this.rotation + "deg)";
};
}
You can use quill-react-commercial.
You can upload, resize, add remark, delete, align iamges. But not rotate.