Skip to content

Commit 539b1c9

Browse files
committed
Implamented resize image handles
1 parent d7f0afb commit 539b1c9

4 files changed

Lines changed: 107 additions & 1 deletion

File tree

src/index.css

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
overflow: hidden;
99
margin-bottom: 10px;
1010
padding-bottom: 0;
11+
position: relative;
1112

1213
&-picture {
1314
max-width: 100%;
@@ -42,6 +43,30 @@
4243
box-sizing: border-box;
4344
}
4445
}
46+
47+
&-resize-handle {
48+
position: absolute;
49+
top: 50%;
50+
right: 4px;
51+
width: 14px;
52+
height: 120px;
53+
max-height: calc(100% - 8px);
54+
background: rgba(0, 0, 0, 0.35);
55+
border: 2px solid rgba(255, 255, 255, 0.95);
56+
border-radius: 12px;
57+
cursor: ew-resize;
58+
transform: translateY(-50%);
59+
opacity: 0;
60+
transition: opacity 0.15s ease, background 0.15s ease;
61+
}
62+
63+
&:hover &-resize-handle {
64+
opacity: 1;
65+
}
66+
67+
&-resize-handle:hover {
68+
background: rgba(0, 0, 0, 0.5);
69+
}
4570
}
4671

4772
&__caption {
@@ -175,4 +200,4 @@
175200
100% {
176201
transform: rotate(360deg);
177202
}
178-
}
203+
}

src/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,7 @@ export default class ImageTool implements BlockTool {
140140
*/
141141
this._data = {
142142
caption: '',
143+
width: undefined,
143144
withBorder: false,
144145
withBackground: false,
145146
stretched: false,
@@ -223,6 +224,7 @@ export default class ImageTool implements BlockTool {
223224
const caption = this.ui.nodes.caption;
224225

225226
this._data.caption = caption.innerHTML;
227+
this._data.width = this.ui.getWidth();
226228

227229
return this.data;
228230
}
@@ -394,7 +396,9 @@ export default class ImageTool implements BlockTool {
394396
this.image = data.file;
395397

396398
this._data.caption = data.caption || '';
399+
this._data.width = typeof data.width === 'number' ? data.width : undefined;
397400
this.ui.fillCaption(this._data.caption);
401+
this.ui.applyWidth(this._data.width);
398402

399403
ImageTool.tunes.forEach(({ name: tune }) => {
400404
const value = typeof data[tune as keyof ImageToolData] !== 'undefined' ? data[tune as keyof ImageToolData] === true || data[tune as keyof ImageToolData] === 'true' : false;

src/types/types.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,10 @@ export type ImageToolData<Actions = {}, AdditionalFileData = {}> = {
7272
* Caption for the image.
7373
*/
7474
caption: string;
75+
/**
76+
* Optional width (in pixels) applied to the image container.
77+
*/
78+
width?: number;
7579

7680
/**
7781
* Flag indicating whether the image has a border.

src/ui.ts

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,11 @@ interface Nodes {
5656
* Caption element for the image.
5757
*/
5858
caption: HTMLElement;
59+
60+
/**
61+
* Resize handle element.
62+
*/
63+
resizeHandle?: HTMLElement;
5964
}
6065

6166
/**
@@ -112,6 +117,11 @@ export default class Ui {
112117
*/
113118
private readOnly: boolean;
114119

120+
/**
121+
* Tracked width in pixels.
122+
*/
123+
private currentWidth?: number;
124+
115125
/**
116126
* @param ui - image tool Ui module
117127
* @param ui.api - Editor.js API
@@ -244,11 +254,73 @@ export default class Ui {
244254
if (this.nodes.imagePreloader !== undefined) {
245255
this.nodes.imagePreloader.style.backgroundImage = '';
246256
}
257+
this.applyWidth(this.currentWidth);
258+
this.ensureResizeHandle();
247259
});
248260

249261
this.nodes.imageContainer.appendChild(this.nodes.imageEl);
250262
}
251263

264+
/**
265+
* Apply a specific width (px) to the image container.
266+
*/
267+
public applyWidth(width?: number): void {
268+
const MIN = 40;
269+
const parentWidth = this.nodes.wrapper.parentElement?.getBoundingClientRect()?.width;
270+
const max = parentWidth && Number.isFinite(parentWidth) ? Math.max(MIN, parentWidth) : undefined;
271+
let next = width;
272+
if (typeof next === 'number' && next > 0) {
273+
if (max) next = Math.min(next, max);
274+
next = Math.max(MIN, next);
275+
this.currentWidth = next;
276+
this.nodes.imageContainer.style.width = `${next}px`;
277+
} else {
278+
this.currentWidth = undefined;
279+
this.nodes.imageContainer.style.width = '';
280+
}
281+
}
282+
283+
/**
284+
* Returns current width if set.
285+
*/
286+
public getWidth(): number | undefined {
287+
if (typeof this.currentWidth === 'number') return this.currentWidth;
288+
const inline = parseFloat(this.nodes.imageContainer.style.width);
289+
return Number.isFinite(inline) ? inline : undefined;
290+
}
291+
292+
/**
293+
* Ensure resize handle exists and is wired.
294+
*/
295+
private ensureResizeHandle(): void {
296+
if (this.readOnly || this.nodes.resizeHandle) return;
297+
const handle = make('div', this.CSS.resizeHandle);
298+
let startX = 0;
299+
let startWidth = 0;
300+
301+
const onMove = (event: MouseEvent) => {
302+
const delta = event.clientX - startX;
303+
const next = startWidth + delta;
304+
this.applyWidth(next);
305+
};
306+
307+
const onUp = () => {
308+
document.removeEventListener('mousemove', onMove);
309+
document.removeEventListener('mouseup', onUp);
310+
};
311+
312+
handle.addEventListener('mousedown', (event: MouseEvent) => {
313+
event.preventDefault();
314+
startX = event.clientX;
315+
startWidth = this.nodes.imageContainer.getBoundingClientRect().width;
316+
document.addEventListener('mousemove', onMove);
317+
document.addEventListener('mouseup', onUp);
318+
});
319+
320+
this.nodes.imageContainer.appendChild(handle);
321+
this.nodes.resizeHandle = handle;
322+
}
323+
252324
/**
253325
* Shows caption input
254326
* @param text - caption content text
@@ -291,6 +363,7 @@ export default class Ui {
291363
imagePreloader: 'image-tool__image-preloader',
292364
imageEl: 'image-tool__image-picture',
293365
caption: 'image-tool__caption',
366+
resizeHandle: 'image-tool__image-resize-handle',
294367
};
295368
};
296369

0 commit comments

Comments
 (0)