resize-quill-image 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +193 -0
- package/dist/index.cjs.js +3 -0
- package/dist/index.d.ts +26 -0
- package/dist/index.es.js +258 -0
- package/package.json +50 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 littlenines
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
<p align="center"> <h1 align="center">resize-quill-image</h1> </p>
|
|
2
|
+
|
|
3
|
+
*`resize-quill-image` is a lightweight Quill module that enables image resizing inside the editor.
|
|
4
|
+
|
|
5
|
+
This module was originally created because we needed a working image resize solution for our own project.
|
|
6
|
+
Most existing Quill modules were either deprecated or not updated for the latest Quill versions.
|
|
7
|
+
While there are a few small issues, they’re usually project-specific and not caused by the module itself.
|
|
8
|
+
You can find these cases and their solutions in the [Problems](#problems) section.*
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
- [Installation](#installation)
|
|
12
|
+
- [Usage](#usage)
|
|
13
|
+
- [Import the module](#1-import-the-module)
|
|
14
|
+
- [Register the module with Quill](#2-register-the-module-with-quill)
|
|
15
|
+
- [Configure the Quill editor](#3-configure-the-quill-editor)
|
|
16
|
+
- [Options](#options)
|
|
17
|
+
- [Option Descriptions](#option-descriptions)
|
|
18
|
+
- [Cleanup / Destroy](#cleanup--destroy)
|
|
19
|
+
- [Usage](#usage-1)
|
|
20
|
+
- [When to use](#when-to-use)
|
|
21
|
+
- [Example with unmount](#example-with-unmount)
|
|
22
|
+
- [Problems](#problems)
|
|
23
|
+
- [Resize overlay goes outside the editor](#problem-resize-overlay-goes-outside-the-editor)
|
|
24
|
+
- [Image resize handles and border do not appear](#problem-image-resize-handles-and-border-do-not-appear)
|
|
25
|
+
- [License](#license)
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
## Demo GIF
|
|
29
|
+

|
|
30
|
+
|
|
31
|
+
## 📦 [Installation](#installation)
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
npm install resize-quill-image
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## [Usage](#usage)
|
|
40
|
+
|
|
41
|
+
### 1. Import the module
|
|
42
|
+
|
|
43
|
+
```js
|
|
44
|
+
import ImageResize from 'resize-quill-image';
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### 2. Register the module with Quill
|
|
48
|
+
|
|
49
|
+
```js
|
|
50
|
+
Quill.register('modules/imageResize', ImageResize);
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### 3. Configure the Quill editor
|
|
54
|
+
|
|
55
|
+
```js
|
|
56
|
+
const quill = new Quill(editorContainer, {
|
|
57
|
+
modules: {
|
|
58
|
+
syntax: true,
|
|
59
|
+
toolbar: toolbarOptions,
|
|
60
|
+
imageResize: true
|
|
61
|
+
},
|
|
62
|
+
placeholder: 'Compose an epic...',
|
|
63
|
+
theme: 'snow'
|
|
64
|
+
});
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## [Options](#options)
|
|
70
|
+
|
|
71
|
+
You can configure the behavior of `resize-quill-image` by passing options inside your Quill config:
|
|
72
|
+
|
|
73
|
+
```js
|
|
74
|
+
imageResize: {
|
|
75
|
+
helpIcon: true,
|
|
76
|
+
displaySize: true,
|
|
77
|
+
styleSelection: true
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### [Option Descriptions](#option-descriptions)
|
|
82
|
+
|
|
83
|
+
| Option | Default | Description |
|
|
84
|
+
|------------------|---------|------------------------------------------------------------------------------------------------------------------|
|
|
85
|
+
| `helpIcon` | `true` | Shows a `?` icon on the image overlay. Describes keyboard shortcuts:<br>• `Shift` → vertical<br>• `Alt` → horizontal<br>• `Ctrl` → custom/free resize <br>  |
|
|
86
|
+
| `displaySize` | `true` | Displays current image width and height as a badge while resizing. <br>  |
|
|
87
|
+
| `styleSelection` | `true` | Disables the blue selection overlay. To keep default behavior: `imageResize: { styleSelection: false }` <br>  |
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
## [Cleanup / Destroy](#cleanup--destroy)
|
|
92
|
+
|
|
93
|
+
If you're dynamically mounting and unmounting the Quill editor (for example in a Single Page Application or during route changes), it's important to properly **clean up** the `resize-quill-image` module to avoid memory leaks or event duplication.
|
|
94
|
+
|
|
95
|
+
This module provides a `destroy()` method that you can call when tearing down your Quill instance.
|
|
96
|
+
|
|
97
|
+
### [Usage](#usage-1)
|
|
98
|
+
|
|
99
|
+
Call the `destroy()` method on the `imageResize` module instance:
|
|
100
|
+
|
|
101
|
+
```js
|
|
102
|
+
const imageResizeModule = quill.getModule('imageResize');
|
|
103
|
+
|
|
104
|
+
if (imageResizeModule?.destroy) {
|
|
105
|
+
imageResizeModule.destroy();
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
This will:
|
|
110
|
+
|
|
111
|
+
- Remove all event listeners (`click`, `selection-change`, `text-change`)
|
|
112
|
+
- Remove any visible resize overlays or handles
|
|
113
|
+
- Clean up drag and tooltip controllers
|
|
114
|
+
- Reset internal references for garbage collection
|
|
115
|
+
|
|
116
|
+
### [When to use](#when-to-use)
|
|
117
|
+
|
|
118
|
+
- If you're unmounting your editor component
|
|
119
|
+
- If you're switching pages in an SPA
|
|
120
|
+
- If you're reinitializing Quill manually
|
|
121
|
+
|
|
122
|
+
### [Example with unmount](#example-with-unmount)
|
|
123
|
+
|
|
124
|
+
```js
|
|
125
|
+
useEffect(() => {
|
|
126
|
+
const quill = new Quill(editorRef.current, { ... });
|
|
127
|
+
|
|
128
|
+
return () => {
|
|
129
|
+
const module = quill.getModule('imageResize');
|
|
130
|
+
if (module?.destroy) module.destroy();
|
|
131
|
+
};
|
|
132
|
+
}, []);
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
This helps ensure your editor stays clean and efficient across mounts.
|
|
136
|
+
|
|
137
|
+
---
|
|
138
|
+
|
|
139
|
+
## [Problems](#problems)
|
|
140
|
+
|
|
141
|
+
### <ins>Problem: Resize overlay goes outside the editor</ins>
|
|
142
|
+
|
|
143
|
+
If your `.ql-container` has a fixed height like this:
|
|
144
|
+
|
|
145
|
+
```css
|
|
146
|
+
.ql-container {
|
|
147
|
+
height: 500px;
|
|
148
|
+
}
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
Then the resize overlay may appear cut off or outside the bounds of the editor when the image goes beyond the height.
|
|
152
|
+
|
|
153
|
+
**Solution:**
|
|
154
|
+
|
|
155
|
+
Instead of a fixed height, use `min-height` and `max-height`:
|
|
156
|
+
|
|
157
|
+
```css
|
|
158
|
+
.ql-container {
|
|
159
|
+
min-height: 500px;
|
|
160
|
+
max-height: 500px;
|
|
161
|
+
}
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
This keeps the resize functionality working correctly and fully visible.
|
|
165
|
+
|
|
166
|
+
### <ins>Problem: Image resize handles and border do not appear</ins>
|
|
167
|
+
|
|
168
|
+
If you don't see the image resize handles or overlay border, make sure you’ve included `highlight.js`.
|
|
169
|
+
Quill’s `syntax` module depends on it, and without it, modules like `imageResize` may silently fail to render overlays.
|
|
170
|
+
|
|
171
|
+
Add this to your HTML:
|
|
172
|
+
|
|
173
|
+
```html
|
|
174
|
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
More info in the Quill docs for newer highlight.js cdn version:
|
|
178
|
+
https://quilljs.com/docs/modules/syntax#syntax-highlighter-module
|
|
179
|
+
|
|
180
|
+
## [License](#license)
|
|
181
|
+
|
|
182
|
+
MIT License
|
|
183
|
+
Free for personal and commercial use.
|
|
184
|
+
|
|
185
|
+
---
|
|
186
|
+
> [!NOTE]
|
|
187
|
+
> If you encounter any bugs, memory leaks, or unexpected behavior, feel free to open an issue on the [GitHub repository](https://github.com/littlenines/resize-quill-image/issues).
|
|
188
|
+
> Your feedback helps make the module better for everyone.
|
|
189
|
+
> If you want to contribute to the project — whether it's fixing a bug or improving performance — your contributions are welcome and appreciated.
|
|
190
|
+
|
|
191
|
+
> [!TIP]
|
|
192
|
+
> If you just want the code and prefer to build your own module on top of it, you're free to do that.
|
|
193
|
+
> Everything is located in the `/src` directory for full access and customization.
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
"use strict";const y=require("quill"),r="#AED2FF",f="#687EFF",c="#fff",h={minWidth:16,minHeight:16,noSelectionClass:"no-selection",handleStyles:{position:"absolute",width:"15px",height:"15px",backgroundColor:r,border:`1px solid ${f}`},overlayStyles:{position:"absolute",boxSizing:"border-box",border:"1px dashed #777",zIndex:8},positions:[{top:0,left:0,clipPath:"polygon(0% 0%, 100% 0%, 0% 100%)"},{top:0,right:0,clipPath:"polygon(100% 0%, 0% 0%, 100% 100%)"},{bottom:0,left:0,clipPath:"polygon(0% 100%, 100% 100%, 0% 0%)"},{bottom:0,right:0,clipPath:"polygon(100% 100%, 0% 100%, 100% 0%)"}],displaySizeStyles:{position:"absolute",fontSize:"12px",backgroundColor:r,color:c,padding:"2px 4px",borderRadius:"4px",userSelect:"none",pointerEvents:"none"},displaySizePositionStyles:{right:"20px",bottom:"5px",left:"auto"},tooltip:{iconStyles:{position:"absolute",top:"8px",right:"8px",width:"20px",height:"20px",background:r,color:c,borderRadius:"50%",textAlign:"center",lineHeight:"20px",fontSize:"14px",cursor:"pointer",userSelect:"none"},textStyles:{position:"absolute",background:r,color:c,padding:"6px 8px",borderRadius:"4px",display:"none",pointerEvents:"none",fontSize:"12px",whiteSpace:"pre"}}};class v{constructor(t,e){this.parent=t,this.styles=e,this.overlay=null}create(){this.overlay=document.createElement("div"),Object.assign(this.overlay.style,this.styles),this.parent.appendChild(this.overlay)}remove(){this.overlay&&(this.overlay.remove(),this.overlay=null)}reposition(t){if(!this.overlay)return;const e=t.getBoundingClientRect(),i=this.parent.getBoundingClientRect();Object.assign(this.overlay.style,{left:`${e.left-i.left-1+this.parent.scrollLeft}px`,top:`${e.top-i.top+this.parent.scrollTop}px`,width:`${e.width}px`,height:`${e.height}px`})}}class M{constructor(t,e,i,n){this.overlay=t,this.positions=e,this.handleStyles=i,this.mousedownCallback=n,this.boxes=[]}createHandles(){this.positions.forEach((t,e)=>{const i=document.createElement("div");Object.assign(i.style,this.handleStyles,t),i.style.clipPath=t.clipPath,e===0||e===3?i.style.cursor="nwse-resize":(e===1||e===2)&&(i.style.cursor="nesw-resize"),i.addEventListener("mousedown",this.mousedownCallback,!1),i.addEventListener("touchstart",this.mousedownCallback,{passive:!1}),this.overlay.appendChild(i),this.boxes.push(i)})}removeHandles(){this.boxes.forEach(t=>{t.removeEventListener("mousedown",this.mousedownCallback),t.removeEventListener("touchstart",this.mousedownCallback),t.onmousedown=null,t.ontouchstart=null,t.remove()}),this.boxes=[],this.overlay=null,this.positions=null,this.handleStyles=null,this.mousedownCallback=null}}class S{constructor(t,e,i,n,a){this.minWidth=t,this.minHeight=e,this.overlayManager=i,this.displaySizeManager=n,this.tooltipInfoManager=a,this.img=null,this.dragBox=null,this.startX=0,this.startY=0,this.startWidth=0,this.startHeight=0,this.handleDrag=this.handleDrag.bind(this),this.handleMouseup=this.handleMouseup.bind(this)}setDisplaySizeManager(t){this.displaySizeManager=t}setTooltipInfoManager(t){this.tooltipInfoManager=t}addEventListeners(){document.addEventListener("mousemove",this.handleDrag),document.addEventListener("touchmove",this.handleDrag,{passive:!1}),document.addEventListener("mouseup",this.handleMouseup,!0),document.addEventListener("touchend",this.handleMouseup,!0),document.addEventListener("touchcancel",this.handleMouseup,!0)}removeEventListeners(){document.removeEventListener("mousemove",this.handleDrag),document.removeEventListener("touchmove",this.handleDrag),document.removeEventListener("mouseup",this.handleMouseup,!0),document.removeEventListener("touchend",this.handleMouseup,!0),document.removeEventListener("touchcancel",this.handleMouseup,!0)}startDragging(t,e,i){t.preventDefault(),this.img=e,this.dragBox=i,t.type==="touchstart"?(this.startX=t.changedTouches[0].clientX,this.startY=t.changedTouches[0].clientY):(this.startX=t.clientX,this.startY=t.clientY),this.startWidth=e.width||e.naturalWidth,this.startHeight=e.height||e.naturalHeight,this.addEventListeners()}handleDrag(t){if(!this.img)return;let e,i;t.type==="touchmove"?(e=t.changedTouches[0].clientX,i=t.changedTouches[0].clientY):(e=t.clientX,i=t.clientY);let n=e-this.startX,a=i-this.startY,p=this.startWidth/this.startHeight;const u=t.ctrlKey,g=t.shiftKey,m=t.altKey;if(u){let s=this.startWidth+n,o=this.startHeight+a;s=Math.max(s,this.minWidth),o=Math.max(o,this.minHeight),this.img.style.width=`${s}px`,this.img.style.height=`${o}px`}else if(g){let s=this.startHeight+a;this.img.style.height=`${Math.max(s,this.minHeight)}px`}else if(m){let s=this.startWidth+n;this.img.style.width=`${Math.max(s,this.minWidth)}px`}else{const s=Math.abs(n)>Math.abs(a)?n:a;let o=this.startWidth+s,d=o/p;o=Math.max(o,this.minWidth),d=Math.max(d,this.minHeight),this.img.style.width=`${o}px`,this.img.style.height=`${d}px`}this.overlayManager&&this.overlayManager.reposition(this.img),this.displaySizeManager&&this.displaySizeManager.update(),this.tooltipInfoManager&&this.tooltipInfoManager.update(),this.startX=e,this.startY=i,this.startWidth=this.img.offsetWidth,this.startHeight=this.img.offsetHeight}handleMouseup(){this.removeEventListeners(),this.img=null,this.dragBox=null}destroy(){this.removeEventListeners(),this.img=null,this.dragBox=null,this.overlayManager=null,this.displaySizeManager=null,this.tooltipInfoManager=null}}class x{constructor(t,e){this.overlay=t,this.img=e,this.display=null}create(){this.display=document.createElement("div"),Object.assign(this.display.style,h.displaySizeStyles),this.overlay.appendChild(this.display),this.update()}update(){if(!this.display||!this.img)return;const t=this.img.offsetWidth,e=this.img.offsetHeight;this.display.innerHTML=`${t} × ${e}`;const i=this.display.getBoundingClientRect();t>120&&e>30?Object.assign(this.display.style,h.displaySizePositionStyles):Object.assign(this.display.style,{right:`-${i.width+4}px`,bottom:"0",left:"auto"})}remove(){this.display&&(this.display.remove(),this.display=null,this.overlay=null,this.img=null)}}class b{constructor(t){this.overlay=t,this.icon=null,this.tooltip=null,this.handleMouseEnter=this.handleMouseEnter.bind(this),this.handleMouseLeave=this.handleMouseLeave.bind(this)}create(){this.icon=document.createElement("div"),this.icon.textContent="?",Object.assign(this.icon.style,h.tooltip.iconStyles),this.tooltip=document.createElement("div"),this.tooltip.textContent=`Shift for vertical
|
|
2
|
+
Ctrl for custom
|
|
3
|
+
Alt for horizontal`,Object.assign(this.tooltip.style,{...h.tooltip.textStyles}),this.icon.addEventListener("mouseenter",this.handleMouseEnter),this.icon.addEventListener("mouseleave",this.handleMouseLeave),this.overlay.appendChild(this.icon),this.overlay.appendChild(this.tooltip),this.update()}handleMouseEnter(){this.tooltip&&(this.tooltip.style.display="block")}handleMouseLeave(){this.tooltip&&(this.tooltip.style.display="none")}update(){if(!this.icon||!this.tooltip)return;const t=this.icon.getBoundingClientRect(),e=this.icon.offsetTop+25,i=t.left-100;Object.assign(this.tooltip.style,{top:`${e}px`,left:`${i}px`})}remove(){!this.icon&&!this.tooltip||(this.icon&&(this.icon.removeEventListener("mouseenter",this.handleMouseEnter),this.icon.removeEventListener("mouseleave",this.handleMouseLeave),this.icon.remove(),this.icon=null),this.tooltip&&(this.tooltip.remove(),this.tooltip=null),this.overlay=null)}}const C=".no-selection::selection{background:transparent!important}",E=l=>{if(typeof document>"u")return;const t=document.createElement("style");t.textContent=l,document.head.appendChild(t)};E(C);class L extends y.Module{constructor(t,e={}){super(t,e),this.quill=t,this.options={helpIcon:!0,displaySize:!0,styleSelection:!0,...h,...e},this.img=null,this.overlayManager=new v(this.quill.root.parentNode,this.options.overlayStyles),this.dragController=new S(this.options.minWidth,this.options.minHeight,this.overlayManager,null,null),this.imageFormat=this.quill.constructor.import("formats/image"),this.bindHandlers(),this.addEventListeners()}bindHandlers(){this.handleClick=this.handleClick.bind(this),this.handleSelectionChange=this.handleSelectionChange.bind(this),this.handleTextChange=this.handleTextChange.bind(this),this.handleMousedown=this.handleMousedown.bind(this)}addEventListeners(){this.quill.root.addEventListener("click",this.handleClick),this.quill.on("selection-change",this.handleSelectionChange),this.quill.on("text-change",this.handleTextChange)}removeEventListeners(){this.quill.root.removeEventListener("click",this.handleClick),this.quill.off("selection-change",this.handleSelectionChange),this.quill.off("text-change",this.handleTextChange)}handleClick(t){if(t.target.tagName==="IMG"){const e=this.quill.constructor.find(t.target);e&&this.quill.setSelection(e.offset(this.quill.scroll),e.length())}}handleSelectionChange(t){if(!t)return this.hide();const[e]=this.quill.scroll.descendant(this.quill.constructor.import("formats/image"),t.index);e&&e.domNode instanceof HTMLImageElement?(this.disableTextSelection(),this.show(e.domNode)):(this.enableTextSelection(),this.hide())}disableTextSelection(){this.options.styleSelection&&this.quill.root.classList.add(this.options.noSelectionClass)}enableTextSelection(){this.options.styleSelection&&this.quill.root.classList.remove(this.options.noSelectionClass)}handleTextChange(){this.img&&(this.quill.root.contains(this.img)?(this.overlayManager.reposition(this.img),this.displaySizeManager&&this.displaySizeManager.update(),this.tooltipInfoManager&&this.tooltipInfoManager.update()):(this.hide(),this.enableTextSelection()))}show(t){this.img!==t&&(!t||!(t instanceof HTMLImageElement)||(this.hide(),this.img=t,this.overlayManager.overlay||this.overlayManager.create(),this.handleManager=new M(this.overlayManager.overlay,this.options.positions,this.options.handleStyles,this.handleMousedown),this.handleManager.createHandles(),this.overlayManager.reposition(this.img),this.options.displaySize&&(this.displaySizeManager=new x(this.overlayManager.overlay,this.img),this.displaySizeManager.create(),this.dragController.setDisplaySizeManager(this.displaySizeManager)),this.options.helpIcon&&(this.tooltipInfoManager=new b(this.overlayManager.overlay),this.tooltipInfoManager.create(),this.dragController.setTooltipInfoManager(this.tooltipInfoManager))))}hide(){this.dragController.setDisplaySizeManager(null),this.dragController.setTooltipInfoManager(null),this.handleManager&&this.handleManager.removeHandles(),this.overlayManager.remove(),this.displaySizeManager&&this.displaySizeManager.remove(),this.displaySizeManager=null,this.tooltipInfoManager&&(this.tooltipInfoManager.remove(),this.tooltipInfoManager=null),this.img=null}handleMousedown(t){t.target instanceof HTMLElement&&this.dragController.startDragging(t,this.img,t.target)}destroy(){var t;this.removeEventListeners(),this.hide(),(t=this.dragController)==null||t.destroy(),this.dragController=null}}module.exports=L;
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
declare module 'resize-quill-image' {
|
|
2
|
+
import { Module } from 'quill';
|
|
3
|
+
|
|
4
|
+
interface ImageResizeOptions {
|
|
5
|
+
helpIcon?: boolean;
|
|
6
|
+
displaySize?: boolean;
|
|
7
|
+
styleSelection?: boolean;
|
|
8
|
+
noSelectionClass?: string;
|
|
9
|
+
minWidth?: number;
|
|
10
|
+
minHeight?: number;
|
|
11
|
+
overlayStyles?: Record<string, any>;
|
|
12
|
+
handleStyles?: Record<string, any>;
|
|
13
|
+
positions?: Array<{
|
|
14
|
+
top?: number;
|
|
15
|
+
left?: number;
|
|
16
|
+
right?: number;
|
|
17
|
+
bottom?: number;
|
|
18
|
+
clipPath?: string;
|
|
19
|
+
}>;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export default class ImageResize extends Module {
|
|
23
|
+
constructor(quill: any, options?: ImageResizeOptions);
|
|
24
|
+
destroy(): void;
|
|
25
|
+
}
|
|
26
|
+
}
|
package/dist/index.es.js
ADDED
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
import { Module as y } from "quill";
|
|
2
|
+
const r = "#AED2FF", f = "#687EFF", c = "#fff", h = {
|
|
3
|
+
minWidth: 16,
|
|
4
|
+
minHeight: 16,
|
|
5
|
+
noSelectionClass: "no-selection",
|
|
6
|
+
handleStyles: {
|
|
7
|
+
position: "absolute",
|
|
8
|
+
width: "15px",
|
|
9
|
+
height: "15px",
|
|
10
|
+
backgroundColor: r,
|
|
11
|
+
border: `1px solid ${f}`
|
|
12
|
+
},
|
|
13
|
+
overlayStyles: {
|
|
14
|
+
position: "absolute",
|
|
15
|
+
boxSizing: "border-box",
|
|
16
|
+
border: "1px dashed #777",
|
|
17
|
+
zIndex: 8
|
|
18
|
+
},
|
|
19
|
+
positions: [
|
|
20
|
+
{ top: 0, left: 0, clipPath: "polygon(0% 0%, 100% 0%, 0% 100%)" },
|
|
21
|
+
{ top: 0, right: 0, clipPath: "polygon(100% 0%, 0% 0%, 100% 100%)" },
|
|
22
|
+
{ bottom: 0, left: 0, clipPath: "polygon(0% 100%, 100% 100%, 0% 0%)" },
|
|
23
|
+
{ bottom: 0, right: 0, clipPath: "polygon(100% 100%, 0% 100%, 100% 0%)" }
|
|
24
|
+
],
|
|
25
|
+
displaySizeStyles: {
|
|
26
|
+
position: "absolute",
|
|
27
|
+
fontSize: "12px",
|
|
28
|
+
backgroundColor: r,
|
|
29
|
+
color: c,
|
|
30
|
+
padding: "2px 4px",
|
|
31
|
+
borderRadius: "4px",
|
|
32
|
+
userSelect: "none",
|
|
33
|
+
pointerEvents: "none"
|
|
34
|
+
},
|
|
35
|
+
displaySizePositionStyles: {
|
|
36
|
+
right: "20px",
|
|
37
|
+
bottom: "5px",
|
|
38
|
+
left: "auto"
|
|
39
|
+
},
|
|
40
|
+
tooltip: {
|
|
41
|
+
iconStyles: {
|
|
42
|
+
position: "absolute",
|
|
43
|
+
top: "8px",
|
|
44
|
+
right: "8px",
|
|
45
|
+
width: "20px",
|
|
46
|
+
height: "20px",
|
|
47
|
+
background: r,
|
|
48
|
+
color: c,
|
|
49
|
+
borderRadius: "50%",
|
|
50
|
+
textAlign: "center",
|
|
51
|
+
lineHeight: "20px",
|
|
52
|
+
fontSize: "14px",
|
|
53
|
+
cursor: "pointer",
|
|
54
|
+
userSelect: "none"
|
|
55
|
+
},
|
|
56
|
+
textStyles: {
|
|
57
|
+
position: "absolute",
|
|
58
|
+
background: r,
|
|
59
|
+
color: c,
|
|
60
|
+
padding: "6px 8px",
|
|
61
|
+
borderRadius: "4px",
|
|
62
|
+
display: "none",
|
|
63
|
+
pointerEvents: "none",
|
|
64
|
+
fontSize: "12px",
|
|
65
|
+
whiteSpace: "pre"
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
class v {
|
|
70
|
+
constructor(t, e) {
|
|
71
|
+
this.parent = t, this.styles = e, this.overlay = null;
|
|
72
|
+
}
|
|
73
|
+
create() {
|
|
74
|
+
this.overlay = document.createElement("div"), Object.assign(this.overlay.style, this.styles), this.parent.appendChild(this.overlay);
|
|
75
|
+
}
|
|
76
|
+
remove() {
|
|
77
|
+
this.overlay && (this.overlay.remove(), this.overlay = null);
|
|
78
|
+
}
|
|
79
|
+
reposition(t) {
|
|
80
|
+
if (!this.overlay) return;
|
|
81
|
+
const e = t.getBoundingClientRect(), i = this.parent.getBoundingClientRect();
|
|
82
|
+
Object.assign(this.overlay.style, {
|
|
83
|
+
left: `${e.left - i.left - 1 + this.parent.scrollLeft}px`,
|
|
84
|
+
top: `${e.top - i.top + this.parent.scrollTop}px`,
|
|
85
|
+
width: `${e.width}px`,
|
|
86
|
+
height: `${e.height}px`
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
class M {
|
|
91
|
+
constructor(t, e, i, n) {
|
|
92
|
+
this.overlay = t, this.positions = e, this.handleStyles = i, this.mousedownCallback = n, this.boxes = [];
|
|
93
|
+
}
|
|
94
|
+
createHandles() {
|
|
95
|
+
this.positions.forEach((t, e) => {
|
|
96
|
+
const i = document.createElement("div");
|
|
97
|
+
Object.assign(i.style, this.handleStyles, t), i.style.clipPath = t.clipPath, e === 0 || e === 3 ? i.style.cursor = "nwse-resize" : (e === 1 || e === 2) && (i.style.cursor = "nesw-resize"), i.addEventListener("mousedown", this.mousedownCallback, !1), i.addEventListener("touchstart", this.mousedownCallback, { passive: !1 }), this.overlay.appendChild(i), this.boxes.push(i);
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
removeHandles() {
|
|
101
|
+
this.boxes.forEach((t) => {
|
|
102
|
+
t.removeEventListener("mousedown", this.mousedownCallback), t.removeEventListener("touchstart", this.mousedownCallback), t.onmousedown = null, t.ontouchstart = null, t.remove();
|
|
103
|
+
}), this.boxes = [], this.overlay = null, this.positions = null, this.handleStyles = null, this.mousedownCallback = null;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
class S {
|
|
107
|
+
constructor(t, e, i, n, a) {
|
|
108
|
+
this.minWidth = t, this.minHeight = e, this.overlayManager = i, this.displaySizeManager = n, this.tooltipInfoManager = a, this.img = null, this.dragBox = null, this.startX = 0, this.startY = 0, this.startWidth = 0, this.startHeight = 0, this.handleDrag = this.handleDrag.bind(this), this.handleMouseup = this.handleMouseup.bind(this);
|
|
109
|
+
}
|
|
110
|
+
setDisplaySizeManager(t) {
|
|
111
|
+
this.displaySizeManager = t;
|
|
112
|
+
}
|
|
113
|
+
setTooltipInfoManager(t) {
|
|
114
|
+
this.tooltipInfoManager = t;
|
|
115
|
+
}
|
|
116
|
+
addEventListeners() {
|
|
117
|
+
document.addEventListener("mousemove", this.handleDrag), document.addEventListener("touchmove", this.handleDrag, { passive: !1 }), document.addEventListener("mouseup", this.handleMouseup, !0), document.addEventListener("touchend", this.handleMouseup, !0), document.addEventListener("touchcancel", this.handleMouseup, !0);
|
|
118
|
+
}
|
|
119
|
+
removeEventListeners() {
|
|
120
|
+
document.removeEventListener("mousemove", this.handleDrag), document.removeEventListener("touchmove", this.handleDrag), document.removeEventListener("mouseup", this.handleMouseup, !0), document.removeEventListener("touchend", this.handleMouseup, !0), document.removeEventListener("touchcancel", this.handleMouseup, !0);
|
|
121
|
+
}
|
|
122
|
+
startDragging(t, e, i) {
|
|
123
|
+
t.preventDefault(), this.img = e, this.dragBox = i, t.type === "touchstart" ? (this.startX = t.changedTouches[0].clientX, this.startY = t.changedTouches[0].clientY) : (this.startX = t.clientX, this.startY = t.clientY), this.startWidth = e.width || e.naturalWidth, this.startHeight = e.height || e.naturalHeight, this.addEventListeners();
|
|
124
|
+
}
|
|
125
|
+
handleDrag(t) {
|
|
126
|
+
if (!this.img) return;
|
|
127
|
+
let e, i;
|
|
128
|
+
t.type === "touchmove" ? (e = t.changedTouches[0].clientX, i = t.changedTouches[0].clientY) : (e = t.clientX, i = t.clientY);
|
|
129
|
+
let n = e - this.startX, a = i - this.startY, p = this.startWidth / this.startHeight;
|
|
130
|
+
const u = t.ctrlKey, g = t.shiftKey, m = t.altKey;
|
|
131
|
+
if (u) {
|
|
132
|
+
let s = this.startWidth + n, o = this.startHeight + a;
|
|
133
|
+
s = Math.max(s, this.minWidth), o = Math.max(o, this.minHeight), this.img.style.width = `${s}px`, this.img.style.height = `${o}px`;
|
|
134
|
+
} else if (g) {
|
|
135
|
+
let s = this.startHeight + a;
|
|
136
|
+
this.img.style.height = `${Math.max(s, this.minHeight)}px`;
|
|
137
|
+
} else if (m) {
|
|
138
|
+
let s = this.startWidth + n;
|
|
139
|
+
this.img.style.width = `${Math.max(s, this.minWidth)}px`;
|
|
140
|
+
} else {
|
|
141
|
+
const s = Math.abs(n) > Math.abs(a) ? n : a;
|
|
142
|
+
let o = this.startWidth + s, d = o / p;
|
|
143
|
+
o = Math.max(o, this.minWidth), d = Math.max(d, this.minHeight), this.img.style.width = `${o}px`, this.img.style.height = `${d}px`;
|
|
144
|
+
}
|
|
145
|
+
this.overlayManager && this.overlayManager.reposition(this.img), this.displaySizeManager && this.displaySizeManager.update(), this.tooltipInfoManager && this.tooltipInfoManager.update(), this.startX = e, this.startY = i, this.startWidth = this.img.offsetWidth, this.startHeight = this.img.offsetHeight;
|
|
146
|
+
}
|
|
147
|
+
handleMouseup() {
|
|
148
|
+
this.removeEventListeners(), this.img = null, this.dragBox = null;
|
|
149
|
+
}
|
|
150
|
+
destroy() {
|
|
151
|
+
this.removeEventListeners(), this.img = null, this.dragBox = null, this.overlayManager = null, this.displaySizeManager = null, this.tooltipInfoManager = null;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
class x {
|
|
155
|
+
constructor(t, e) {
|
|
156
|
+
this.overlay = t, this.img = e, this.display = null;
|
|
157
|
+
}
|
|
158
|
+
create() {
|
|
159
|
+
this.display = document.createElement("div"), Object.assign(this.display.style, h.displaySizeStyles), this.overlay.appendChild(this.display), this.update();
|
|
160
|
+
}
|
|
161
|
+
update() {
|
|
162
|
+
if (!this.display || !this.img) return;
|
|
163
|
+
const t = this.img.offsetWidth, e = this.img.offsetHeight;
|
|
164
|
+
this.display.innerHTML = `${t} × ${e}`;
|
|
165
|
+
const i = this.display.getBoundingClientRect();
|
|
166
|
+
t > 120 && e > 30 ? Object.assign(this.display.style, h.displaySizePositionStyles) : Object.assign(this.display.style, {
|
|
167
|
+
right: `-${i.width + 4}px`,
|
|
168
|
+
bottom: "0",
|
|
169
|
+
left: "auto"
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
remove() {
|
|
173
|
+
this.display && (this.display.remove(), this.display = null, this.overlay = null, this.img = null);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
class b {
|
|
177
|
+
constructor(t) {
|
|
178
|
+
this.overlay = t, this.icon = null, this.tooltip = null, this.handleMouseEnter = this.handleMouseEnter.bind(this), this.handleMouseLeave = this.handleMouseLeave.bind(this);
|
|
179
|
+
}
|
|
180
|
+
create() {
|
|
181
|
+
this.icon = document.createElement("div"), this.icon.textContent = "?", Object.assign(this.icon.style, h.tooltip.iconStyles), this.tooltip = document.createElement("div"), this.tooltip.textContent = `Shift for vertical
|
|
182
|
+
Ctrl for custom
|
|
183
|
+
Alt for horizontal`, Object.assign(this.tooltip.style, { ...h.tooltip.textStyles }), this.icon.addEventListener("mouseenter", this.handleMouseEnter), this.icon.addEventListener("mouseleave", this.handleMouseLeave), this.overlay.appendChild(this.icon), this.overlay.appendChild(this.tooltip), this.update();
|
|
184
|
+
}
|
|
185
|
+
handleMouseEnter() {
|
|
186
|
+
this.tooltip && (this.tooltip.style.display = "block");
|
|
187
|
+
}
|
|
188
|
+
handleMouseLeave() {
|
|
189
|
+
this.tooltip && (this.tooltip.style.display = "none");
|
|
190
|
+
}
|
|
191
|
+
update() {
|
|
192
|
+
if (!this.icon || !this.tooltip) return;
|
|
193
|
+
const t = this.icon.getBoundingClientRect(), e = this.icon.offsetTop + 25, i = t.left - 100;
|
|
194
|
+
Object.assign(this.tooltip.style, {
|
|
195
|
+
top: `${e}px`,
|
|
196
|
+
left: `${i}px`
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
remove() {
|
|
200
|
+
!this.icon && !this.tooltip || (this.icon && (this.icon.removeEventListener("mouseenter", this.handleMouseEnter), this.icon.removeEventListener("mouseleave", this.handleMouseLeave), this.icon.remove(), this.icon = null), this.tooltip && (this.tooltip.remove(), this.tooltip = null), this.overlay = null);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
const C = ".no-selection::selection{background:transparent!important}", E = (l) => {
|
|
204
|
+
if (typeof document > "u") return;
|
|
205
|
+
const t = document.createElement("style");
|
|
206
|
+
t.textContent = l, document.head.appendChild(t);
|
|
207
|
+
};
|
|
208
|
+
E(C);
|
|
209
|
+
class w extends y {
|
|
210
|
+
constructor(t, e = {}) {
|
|
211
|
+
super(t, e), this.quill = t, this.options = { helpIcon: !0, displaySize: !0, styleSelection: !0, ...h, ...e }, this.img = null, this.overlayManager = new v(this.quill.root.parentNode, this.options.overlayStyles), this.dragController = new S(this.options.minWidth, this.options.minHeight, this.overlayManager, null, null), this.imageFormat = this.quill.constructor.import("formats/image"), this.bindHandlers(), this.addEventListeners();
|
|
212
|
+
}
|
|
213
|
+
bindHandlers() {
|
|
214
|
+
this.handleClick = this.handleClick.bind(this), this.handleSelectionChange = this.handleSelectionChange.bind(this), this.handleTextChange = this.handleTextChange.bind(this), this.handleMousedown = this.handleMousedown.bind(this);
|
|
215
|
+
}
|
|
216
|
+
addEventListeners() {
|
|
217
|
+
this.quill.root.addEventListener("click", this.handleClick), this.quill.on("selection-change", this.handleSelectionChange), this.quill.on("text-change", this.handleTextChange);
|
|
218
|
+
}
|
|
219
|
+
removeEventListeners() {
|
|
220
|
+
this.quill.root.removeEventListener("click", this.handleClick), this.quill.off("selection-change", this.handleSelectionChange), this.quill.off("text-change", this.handleTextChange);
|
|
221
|
+
}
|
|
222
|
+
handleClick(t) {
|
|
223
|
+
if (t.target.tagName === "IMG") {
|
|
224
|
+
const e = this.quill.constructor.find(t.target);
|
|
225
|
+
e && this.quill.setSelection(e.offset(this.quill.scroll), e.length());
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
handleSelectionChange(t) {
|
|
229
|
+
if (!t) return this.hide();
|
|
230
|
+
const [e] = this.quill.scroll.descendant(this.quill.constructor.import("formats/image"), t.index);
|
|
231
|
+
e && e.domNode instanceof HTMLImageElement ? (this.disableTextSelection(), this.show(e.domNode)) : (this.enableTextSelection(), this.hide());
|
|
232
|
+
}
|
|
233
|
+
disableTextSelection() {
|
|
234
|
+
this.options.styleSelection && this.quill.root.classList.add(this.options.noSelectionClass);
|
|
235
|
+
}
|
|
236
|
+
enableTextSelection() {
|
|
237
|
+
this.options.styleSelection && this.quill.root.classList.remove(this.options.noSelectionClass);
|
|
238
|
+
}
|
|
239
|
+
handleTextChange() {
|
|
240
|
+
this.img && (this.quill.root.contains(this.img) ? (this.overlayManager.reposition(this.img), this.displaySizeManager && this.displaySizeManager.update(), this.tooltipInfoManager && this.tooltipInfoManager.update()) : (this.hide(), this.enableTextSelection()));
|
|
241
|
+
}
|
|
242
|
+
show(t) {
|
|
243
|
+
this.img !== t && (!t || !(t instanceof HTMLImageElement) || (this.hide(), this.img = t, this.overlayManager.overlay || this.overlayManager.create(), this.handleManager = new M(this.overlayManager.overlay, this.options.positions, this.options.handleStyles, this.handleMousedown), this.handleManager.createHandles(), this.overlayManager.reposition(this.img), this.options.displaySize && (this.displaySizeManager = new x(this.overlayManager.overlay, this.img), this.displaySizeManager.create(), this.dragController.setDisplaySizeManager(this.displaySizeManager)), this.options.helpIcon && (this.tooltipInfoManager = new b(this.overlayManager.overlay), this.tooltipInfoManager.create(), this.dragController.setTooltipInfoManager(this.tooltipInfoManager))));
|
|
244
|
+
}
|
|
245
|
+
hide() {
|
|
246
|
+
this.dragController.setDisplaySizeManager(null), this.dragController.setTooltipInfoManager(null), this.handleManager && this.handleManager.removeHandles(), this.overlayManager.remove(), this.displaySizeManager && this.displaySizeManager.remove(), this.displaySizeManager = null, this.tooltipInfoManager && (this.tooltipInfoManager.remove(), this.tooltipInfoManager = null), this.img = null;
|
|
247
|
+
}
|
|
248
|
+
handleMousedown(t) {
|
|
249
|
+
t.target instanceof HTMLElement && this.dragController.startDragging(t, this.img, t.target);
|
|
250
|
+
}
|
|
251
|
+
destroy() {
|
|
252
|
+
var t;
|
|
253
|
+
this.removeEventListeners(), this.hide(), (t = this.dragController) == null || t.destroy(), this.dragController = null;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
export {
|
|
257
|
+
w as default
|
|
258
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "resize-quill-image",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "A lightweight Quill module to resize images with handles and overlays",
|
|
5
|
+
"main": "dist/index.cjs.js",
|
|
6
|
+
"module": "dist/index.es.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.es.js",
|
|
12
|
+
"require": "./dist/index.cjs.js"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist",
|
|
17
|
+
"LICENSE",
|
|
18
|
+
"README.md"
|
|
19
|
+
],
|
|
20
|
+
"keywords": [
|
|
21
|
+
"quill",
|
|
22
|
+
"quilljs",
|
|
23
|
+
"quill-module",
|
|
24
|
+
"editor",
|
|
25
|
+
"quill-resize-image",
|
|
26
|
+
"image",
|
|
27
|
+
"resize",
|
|
28
|
+
"quill image resize",
|
|
29
|
+
"resize quill image"
|
|
30
|
+
],
|
|
31
|
+
"repository": {
|
|
32
|
+
"type": "git",
|
|
33
|
+
"url": "https://github.com/littlenines/resize-quill-image.git"
|
|
34
|
+
},
|
|
35
|
+
"homepage": "https://github.com/littlenines/resize-quill-image#readme",
|
|
36
|
+
"bugs": {
|
|
37
|
+
"url": "https://github.com/littlenines/resize-quill-image/issues"
|
|
38
|
+
},
|
|
39
|
+
"author": "littlenines",
|
|
40
|
+
"license": "MIT",
|
|
41
|
+
"scripts": {
|
|
42
|
+
"build": "vite build && node -e \"require('fs').cpSync('src/index.d.ts', 'dist/index.d.ts')\""
|
|
43
|
+
},
|
|
44
|
+
"peerDependencies": {
|
|
45
|
+
"quill": ">=1.3.7 <=2.0.3"
|
|
46
|
+
},
|
|
47
|
+
"devDependencies": {
|
|
48
|
+
"vite": "^6.3.5"
|
|
49
|
+
}
|
|
50
|
+
}
|