feat(messages): paste images into chat + responsive image display
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

- Paste handler: Ctrl+V image → uploads to /api/messages/upload-image → inserts <img>
- Fallback to base64 if upload fails
- Enter sends messages with images (even without text)
- CSS: responsive images in messages (max 300px height, rounded)
- CSS: images in Quill editor resizable

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Maciej Pienczyn 2026-03-30 15:52:29 +02:00
parent 18e4990db5
commit 51c80c78a4
2 changed files with 58 additions and 2 deletions

View File

@ -1244,9 +1244,10 @@
handler: function() { handler: function() {
var html = state.quill.root.innerHTML; var html = state.quill.root.innerHTML;
var text = state.quill.getText().trim(); var text = state.quill.getText().trim();
if (text) { var hasContent = text || html.indexOf('<img') !== -1;
if (hasContent) {
state.quill.setText(''); state.quill.setText('');
Composer.sendContent(html, text); Composer.sendContent(html, text || '(obraz)');
} }
return false; return false;
} }
@ -1266,6 +1267,44 @@
}, },
}); });
// Handle pasted images — upload to server and insert as <img>
state.quill.root.addEventListener('paste', function(e) {
var clipboardData = e.clipboardData || window.clipboardData;
if (!clipboardData || !clipboardData.items) return;
for (var i = 0; i < clipboardData.items.length; i++) {
var item = clipboardData.items[i];
if (item.type.indexOf('image') !== -1) {
e.preventDefault();
e.stopPropagation();
var file = item.getAsFile();
if (file) {
// Upload image as attachment
var fd = new FormData();
fd.append('image', file, 'pasted-image.png');
api('/api/messages/upload-image', 'POST', fd)
.then(function(result) {
if (result && result.url) {
var range = state.quill.getSelection(true);
state.quill.insertEmbed(range.index, 'image', result.url);
state.quill.setSelection(range.index + 1);
}
})
.catch(function(err) {
// Fallback: insert as base64
var reader = new FileReader();
reader.onload = function(ev) {
var range = state.quill.getSelection(true);
state.quill.insertEmbed(range.index, 'image', ev.target.result);
state.quill.setSelection(range.index + 1);
};
reader.readAsDataURL(file);
});
}
return;
}
}
});
// Typing indicator // Typing indicator
state.quill.on('text-change', function () { state.quill.on('text-change', function () {
Composer.sendTyping(); Composer.sendTyping();

View File

@ -10,6 +10,23 @@
<style> <style>
footer { display: none !important; } footer { display: none !important; }
main { padding-bottom: 0 !important; margin-bottom: 0 !important; } main { padding-bottom: 0 !important; margin-bottom: 0 !important; }
/* Images in messages — responsive with max size */
.message-content img {
max-width: 100%;
max-height: 300px;
border-radius: 8px;
cursor: pointer;
object-fit: contain;
}
.message-content img:hover { opacity: 0.9; }
/* Images in Quill editor — resizable */
.ql-editor img {
max-width: 100%;
max-height: 200px;
border-radius: 4px;
resize: both;
overflow: hidden;
}
/* Hide floating buttons that overlap with chat input area */ /* Hide floating buttons that overlap with chat input area */
.pwa-smart-banner, .staging-panel-toggle { display: none !important; } .pwa-smart-banner, .staging-panel-toggle { display: none !important; }
</style> </style>