fix(messages): single image paste + drag-to-resize handles
Some checks are pending
NordaBiz Tests / Unit & Integration Tests (push) Waiting to run
NordaBiz Tests / E2E Tests (Playwright) (push) Blocked by required conditions
NordaBiz Tests / Smoke Tests (Production) (push) Blocked by required conditions
NordaBiz Tests / Send Failure Notification (push) Blocked by required conditions

- Block Quill's default IMG clipboard matcher (prevents double paste)
- Click on image in editor shows blue border + resize handle (bottom-right)
- Drag handle to resize image
- Click outside removes resize wrapper

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Maciej Pienczyn 2026-03-30 16:01:41 +02:00
parent 7c8fd90a21
commit 2145a73bf3

View File

@ -1268,7 +1268,63 @@
}, },
}); });
// Handle pasted images — upload to server and insert as <img> // Image resize: click on image in editor to show resize handles
state.quill.root.addEventListener('click', function(e) {
if (e.target.tagName === 'IMG') {
var img = e.target;
// Remove any existing resize wrapper
document.querySelectorAll('.img-resize-wrapper').forEach(function(w) {
var inner = w.querySelector('img');
if (inner) w.parentNode.replaceChild(inner, w);
});
// Wrap image in resize container
var wrapper = document.createElement('div');
wrapper.className = 'img-resize-wrapper';
wrapper.contentEditable = 'false';
wrapper.style.cssText = 'display:inline-block; position:relative; border:2px solid #3b82f6; padding:2px;';
img.parentNode.insertBefore(wrapper, img);
wrapper.appendChild(img);
// Add resize handle
var handle = document.createElement('div');
handle.style.cssText = 'position:absolute; right:-4px; bottom:-4px; width:12px; height:12px; background:#3b82f6; border-radius:2px; cursor:nwse-resize;';
wrapper.appendChild(handle);
// Drag to resize
handle.addEventListener('mousedown', function(me) {
me.preventDefault();
var startX = me.clientX;
var startW = img.offsetWidth;
function onMove(mm) {
var newW = Math.max(50, startW + (mm.clientX - startX));
img.style.width = newW + 'px';
img.style.height = 'auto';
}
function onUp() {
document.removeEventListener('mousemove', onMove);
document.removeEventListener('mouseup', onUp);
}
document.addEventListener('mousemove', onMove);
document.addEventListener('mouseup', onUp);
});
}
});
// Click outside image removes resize wrapper
document.addEventListener('click', function(e) {
if (!e.target.closest('.img-resize-wrapper') && !e.target.closest('.ql-editor img')) {
document.querySelectorAll('.img-resize-wrapper').forEach(function(w) {
var inner = w.querySelector('img');
if (inner) w.parentNode.replaceChild(inner, w);
});
}
});
// Block Quill's default image paste (prevents double insert)
var Delta = Quill.import('delta');
state.quill.clipboard.addMatcher('IMG', function(node, delta) {
return new Delta(); // Remove pasted images from Quill's clipboard processing
});
// Handle pasted images ourselves — upload to server
state.quill.root.addEventListener('paste', function(e) { state.quill.root.addEventListener('paste', function(e) {
var clipboardData = e.clipboardData || window.clipboardData; var clipboardData = e.clipboardData || window.clipboardData;
if (!clipboardData || !clipboardData.items) return; if (!clipboardData || !clipboardData.items) return;
@ -1276,10 +1332,9 @@
var item = clipboardData.items[i]; var item = clipboardData.items[i];
if (item.type.indexOf('image') !== -1) { if (item.type.indexOf('image') !== -1) {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopImmediatePropagation();
var file = item.getAsFile(); var file = item.getAsFile();
if (file) { if (file) {
// Upload image as attachment
var fd = new FormData(); var fd = new FormData();
fd.append('image', file, 'pasted-image.png'); fd.append('image', file, 'pasted-image.png');
api('/api/messages/upload-image', 'POST', fd) api('/api/messages/upload-image', 'POST', fd)
@ -1291,7 +1346,6 @@
} }
}) })
.catch(function(err) { .catch(function(err) {
// Fallback: insert as base64
var reader = new FileReader(); var reader = new FileReader();
reader.onload = function(ev) { reader.onload = function(ev) {
var range = state.quill.getSelection(true); var range = state.quill.getSelection(true);
@ -1304,7 +1358,7 @@
return; return;
} }
} }
}); }, true);
// Typing indicator // Typing indicator
state.quill.on('text-change', function () { state.quill.on('text-change', function () {