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
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:
parent
1c10052689
commit
7d5d785ca7
@ -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);
|
||||
|
||||
@ -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);
|
||||
},
|
||||
};
|
||||
|
||||
// ============================================================
|
||||
|
||||
@ -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 %}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user