feat(messages): highlight search terms in chat messages with yellow mark, clear button
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

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Maciej Pienczyn 2026-03-27 16:03:09 +01:00
parent 1c10052689
commit 7d5d785ca7
3 changed files with 101 additions and 4 deletions

View File

@ -1032,6 +1032,47 @@
border-radius: 2px;
}
/* --- Search highlight in messages --- */
mark.search-highlight {
background: #fef08a;
color: inherit;
padding: 1px 3px;
border-radius: 3px;
box-shadow: 0 0 0 1px rgba(250, 204, 21, 0.4);
}
.message-row.mine mark.search-highlight {
background: rgba(254, 240, 138, 0.35);
box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.3);
}
.search-highlight-bar {
display: flex;
align-items: center;
justify-content: space-between;
padding: 8px 16px;
background: #fefce8;
border-bottom: 1px solid #fde68a;
font-size: 13px;
color: #854d0e;
}
.search-highlight-clear {
border: 1px solid #fde68a;
background: #fff;
color: #854d0e;
padding: 4px 12px;
border-radius: 6px;
font-size: 12px;
cursor: pointer;
font-family: inherit;
transition: background 0.12s;
}
.search-highlight-clear:hover {
background: #fef9c3;
}
.search-result-date {
font-size: 11px;
color: var(--conv-text-muted);

View File

@ -34,6 +34,7 @@
typingTimeout: null,
reconnectDelay: 1000,
pinnedMessageIds: [], // IDs of pinned messages in current conversation
searchHighlight: null, // Current search query to highlight in chat
isMobile: window.innerWidth <= 768,
};
@ -521,6 +522,12 @@
// Content
var content = el('div', 'message-content');
content.innerHTML = msg.content || '';
// Highlight search term if active
if (state.searchHighlight) {
ChatView.highlightText(content, state.searchHighlight);
}
bubble.appendChild(content);
// Link preview
@ -714,6 +721,36 @@
setTimeout(function () { row.style.background = ''; }, 2000);
}
},
highlightText: function (element, query) {
// Walk text nodes and wrap matches in <mark>
var escapedQuery = query.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
var regex = new RegExp('(' + escapedQuery + ')', 'gi');
var walker = document.createTreeWalker(element, NodeFilter.SHOW_TEXT, null, false);
var textNodes = [];
while (walker.nextNode()) textNodes.push(walker.currentNode);
textNodes.forEach(function (node) {
if (regex.test(node.nodeValue)) {
var span = document.createElement('span');
span.innerHTML = node.nodeValue.replace(regex, '<mark class="search-highlight">$1</mark>');
node.parentNode.replaceChild(span, node);
}
regex.lastIndex = 0;
});
},
clearHighlights: function () {
state.searchHighlight = null;
document.querySelectorAll('mark.search-highlight').forEach(function (m) {
var parent = m.parentNode;
parent.replaceChild(document.createTextNode(m.textContent), m);
parent.normalize();
});
// Remove clear button
var clearBtn = document.getElementById('clearSearchHighlight');
if (clearBtn) clearBtn.remove();
},
};
// ============================================================
@ -1991,15 +2028,18 @@
item.appendChild(dateRow);
item.addEventListener('click', function () {
// Set highlight before loading conversation
state.searchHighlight = query;
Search.clearResults();
var input = document.getElementById('searchInput');
if (input) input.value = '';
ConversationList.searchFilter('');
ConversationList.selectConversation(r.conversation_id);
// Scroll to message after load
// Scroll to message and show clear button after load
setTimeout(function () {
ChatView.scrollToMessage(r.message_id);
}, 500);
Search.showClearHighlightBtn();
}, 600);
});
section.appendChild(item);
@ -2012,6 +2052,22 @@
var existing = document.getElementById('searchResultsSection');
if (existing) existing.remove();
},
showClearHighlightBtn: function () {
if (document.getElementById('clearSearchHighlight')) return;
var chatHeader = document.getElementById('chatHeader');
if (!chatHeader) return;
var bar = el('div', 'search-highlight-bar');
bar.id = 'clearSearchHighlight';
bar.innerHTML = '<span>🔍 Wyniki dla: <strong>' + state.searchHighlight + '</strong></span>';
var clearBtn = el('button', 'search-highlight-clear', '✕ Wyczyść');
clearBtn.addEventListener('click', function () {
ChatView.clearHighlights();
});
bar.appendChild(clearBtn);
chatHeader.parentNode.insertBefore(bar, chatHeader.nextSibling);
},
};
// ============================================================

View File

@ -5,7 +5,7 @@
{% block head_extra %}
<link rel="stylesheet" href="{{ url_for('static', filename='css/quill.snow.css') }}">
<link rel="stylesheet" href="{{ url_for('static', filename='css/conversations.css') }}?v=6">
<link rel="stylesheet" href="{{ url_for('static', filename='css/conversations.css') }}?v=7">
<script src="{{ url_for('static', filename='js/vendor/quill.js') }}"></script>
<style>
footer { display: none !important; }
@ -228,7 +228,7 @@ window.__CSRF_TOKEN__ = '{{ csrf_token() }}';
// Load conversations.js after data is set
(function() {
var s = document.createElement('script');
s.src = '{{ url_for("static", filename="js/conversations.js") }}?v=11';
s.src = '{{ url_for("static", filename="js/conversations.js") }}?v=12';
document.body.appendChild(s);
})();
{% endblock %}