feat(dashboard): fix Ctrl+K shortcut, debounce search queries, and add request cancellation
This commit is contained in:
1 parent
83aa26634d
commit
87ddb01063
1 file changed
+65
-5
@@ -972,19 +972,34 @@
|
|||||||
return text.replace(regex, '<span class="search-highlight">$1</span>');
|
return text.replace(regex, '<span class="search-highlight">$1</span>');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let searchDebounceTimer = null;
|
||||||
|
let activeSearchAbortController = null;
|
||||||
|
|
||||||
async function handleSearch(e) {
|
async function handleSearch(e) {
|
||||||
const q = e.target.value;
|
const q = e.target.value.trim();
|
||||||
// Provide live feedback if they type at least 3 characters, or hit Enter
|
|
||||||
if (e.key === 'Enter' || q.length >= 3 || q.length === 0) {
|
if (searchDebounceTimer) clearTimeout(searchDebounceTimer);
|
||||||
|
|
||||||
if (!q) {
|
if (!q) {
|
||||||
|
if (activeSearchAbortController) activeSearchAbortController.abort();
|
||||||
document.getElementById('search-results').innerHTML = '';
|
document.getElementById('search-results').innerHTML = '';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const res = await fetch(`/api/search?q=${encodeURIComponent(q)}`);
|
|
||||||
|
const delay = (e.key === 'Enter') ? 0 : 250;
|
||||||
|
|
||||||
|
searchDebounceTimer = setTimeout(async () => {
|
||||||
|
if (activeSearchAbortController) activeSearchAbortController.abort();
|
||||||
|
activeSearchAbortController = new AbortController();
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await fetch(`/api/search?q=${encodeURIComponent(q)}`, {
|
||||||
|
signal: activeSearchAbortController.signal
|
||||||
|
});
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
const container = document.getElementById('search-results');
|
const container = document.getElementById('search-results');
|
||||||
|
|
||||||
if (data.results.length === 0) {
|
if (!data.results || data.results.length === 0) {
|
||||||
container.innerHTML = '<div style="padding:15px;color:var(--text-secondary);">No results found.</div>';
|
container.innerHTML = '<div style="padding:15px;color:var(--text-secondary);">No results found.</div>';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -1002,8 +1017,13 @@
|
|||||||
<div style="font-size:0.75em; color:var(--text-secondary); margin-top:8px; font-family:monospace; background: var(--canvas-bg); padding: 2px 4px; border-radius: 3px; display: inline-block;">ID: ${r.id}</div>
|
<div style="font-size:0.75em; color:var(--text-secondary); margin-top:8px; font-family:monospace; background: var(--canvas-bg); padding: 2px 4px; border-radius: 3px; display: inline-block;">ID: ${r.id}</div>
|
||||||
</div>
|
</div>
|
||||||
`).join('');
|
`).join('');
|
||||||
|
} catch (err) {
|
||||||
|
if (err.name !== 'AbortError') {
|
||||||
|
console.error("Search error:", err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}, delay);
|
||||||
|
}
|
||||||
|
|
||||||
// --- WebSocket Activity Feed ---
|
// --- WebSocket Activity Feed ---
|
||||||
const MAX_ACTIVITY_HISTORY = 100;
|
const MAX_ACTIVITY_HISTORY = 100;
|
||||||
@@ -1238,6 +1258,45 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function setupSSE() {
|
||||||
|
try {
|
||||||
|
const sse = new EventSource('/api/activity/stream');
|
||||||
|
sse.onmessage = function(event) {
|
||||||
|
if (event.data) {
|
||||||
|
try {
|
||||||
|
const rec = JSON.parse(event.data);
|
||||||
|
const feed = document.getElementById('activity-feed');
|
||||||
|
if (feed) {
|
||||||
|
const item = document.createElement('div');
|
||||||
|
item.style.padding = '8px 12px';
|
||||||
|
item.style.borderBottom = '1px solid var(--border-color)';
|
||||||
|
item.style.fontSize = '0.85em';
|
||||||
|
item.style.background = 'rgba(39, 174, 96, 0.08)';
|
||||||
|
item.innerHTML = `<strong>${rec.category || 'activity'}</strong>: ${rec.summary || event.data}`;
|
||||||
|
feed.insertBefore(item, feed.firstChild);
|
||||||
|
}
|
||||||
|
} catch(e) {}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} catch(e) { console.error('SSE initialization error', e); }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ctrl+K / Cmd+K Global Search shortcut
|
||||||
|
document.addEventListener('keydown', function(e) {
|
||||||
|
if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === 'k') {
|
||||||
|
e.preventDefault();
|
||||||
|
const searchTabBtn = document.querySelectorAll('.tab-button')[1];
|
||||||
|
if (searchTabBtn) {
|
||||||
|
switchTab('search-tab', searchTabBtn);
|
||||||
|
}
|
||||||
|
const searchInput = document.getElementById('search-box');
|
||||||
|
if (searchInput) {
|
||||||
|
searchInput.focus();
|
||||||
|
searchInput.select();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// --- Start ---
|
// --- Start ---
|
||||||
loadVersion();
|
loadVersion();
|
||||||
loadGraph();
|
loadGraph();
|
||||||
@@ -1246,6 +1305,7 @@
|
|||||||
loadActivityHistory();
|
loadActivityHistory();
|
||||||
loadAllExtras();
|
loadAllExtras();
|
||||||
setupWS();
|
setupWS();
|
||||||
|
setupSSE();
|
||||||
|
|
||||||
// Listen to theme toggle changes to update graph font colors
|
// Listen to theme toggle changes to update graph font colors
|
||||||
const observer = new MutationObserver(() => updateGraphData());
|
const observer = new MutationObserver(() => updateGraphData());
|
||||||
|
|||||||
Reference in new issue
Block a user