fix(messages): tempId-based dedup (not content) + clickable profile in chat header
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

Dedup: sendContent passes tempId through queue to _doSend, which
replaces optimistic msg by matching tempId (not content). Eliminates
false negatives from HTML sanitization differences.

Profile: clicking avatar or name in chat header opens /profil/<user_id>.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Maciej Pienczyn 2026-03-30 14:24:44 +02:00
parent 3931b1466c
commit a52c52863e

View File

@ -388,6 +388,21 @@
var other = details.members.find(function (m) {
return m.user_id !== window.__CURRENT_USER__.id;
});
if (other) {
// Make avatar and name clickable to profile
var headerAvatar = document.getElementById('headerAvatar');
var headerName = document.getElementById('headerName');
var profileUrl = '/profil/' + other.user_id;
if (headerAvatar) {
headerAvatar.style.cursor = 'pointer';
headerAvatar.onclick = function() { window.location.href = profileUrl; };
}
if (headerName) {
headerName.style.cursor = 'pointer';
headerName.onclick = function() { window.location.href = profileUrl; };
headerName.title = 'Otwórz profil';
}
}
if (other && other.is_online) {
subtitle.innerHTML = '<span class="online-status-dot"></span> online';
subtitle.classList.add('is-online');
@ -1295,12 +1310,14 @@
sendContent: function (html, text) {
if (!state.currentConversationId) return;
// Add to queue with current state snapshot
var tempId = 'temp-' + Date.now() + '-' + Math.random();
Composer._sendQueue.push({
convId: state.currentConversationId,
html: html,
text: text,
replyTo: state.replyToMessage,
files: state.attachedFiles.slice()
files: state.attachedFiles.slice(),
tempId: tempId
});
state.attachedFiles = [];
state.replyToMessage = null;
@ -1308,7 +1325,6 @@
if (replyPreview) replyPreview.style.display = 'none';
Composer.renderAttachments();
// Show optimistic message immediately
var tempId = 'temp-' + Date.now() + '-' + Math.random();
ChatView.appendMessage({
id: tempId,
conversation_id: state.currentConversationId,
@ -1327,7 +1343,7 @@
Composer._queueProcessing = true;
while (Composer._sendQueue.length > 0) {
var item = Composer._sendQueue.shift();
await Composer._doSend(item.convId, item.html, item.text, item.replyTo, item.files);
await Composer._doSend(item.convId, item.html, item.text, item.replyTo, item.files, item.tempId);
}
Composer._queueProcessing = false;
},
@ -1355,7 +1371,7 @@
return Composer._doSend(convId, html, text, savedReplyTo, savedFiles);
},
_doSend: async function (convId, html, text, savedReplyTo, savedFiles) {
_doSend: async function (convId, html, text, savedReplyTo, savedFiles, tempId) {
// Optimistic message already shown by sendContent() — just do the API call
try {
var fd = new FormData();
@ -1368,18 +1384,15 @@
var result = await api('/api/conversations/' + convId + '/messages', 'POST', fd);
// Update optimistic message in state with real data (for dedup)
// Replace optimistic message with real data (match by tempId)
var msgs = state.messages[convId];
if (msgs) {
var stripped = (html || '').replace(/<[^>]*>/g, '').trim();
if (msgs && tempId) {
for (var i = msgs.length - 1; i >= 0; i--) {
if (msgs[i]._optimistic && msgs[i].sender_id === result.sender_id) {
var mc = (msgs[i].content || '').replace(/<[^>]*>/g, '').trim();
if (mc === stripped) {
msgs[i].id = result.id;
msgs[i]._optimistic = false;
break;
}
if (msgs[i].id === tempId) {
msgs[i].id = result.id;
msgs[i]._optimistic = false;
msgs[i].content = result.content;
break;
}
}
}