feat(web): add Ctrl+K command palette search modal
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -144,8 +144,13 @@
|
||||
});
|
||||
|
||||
// Global Escape key handler for closing the detail panel
|
||||
// (Search modal Escape is handled separately with stopPropagation)
|
||||
document.addEventListener('keydown', function(e) {
|
||||
if (e.key === 'Escape') {
|
||||
// Don't close detail panel if search modal handled Escape
|
||||
var searchModal = document.getElementById('search-modal');
|
||||
if (searchModal && searchModal.classList.contains('search-backdrop--open')) return;
|
||||
|
||||
var panel = document.querySelector('.detail-panel--open');
|
||||
if (panel) {
|
||||
closeDetailPanel();
|
||||
@@ -154,4 +159,105 @@
|
||||
}
|
||||
});
|
||||
|
||||
// ========================================
|
||||
// Search Modal (Ctrl+K)
|
||||
// ========================================
|
||||
|
||||
var searchSelectedIndex = 0;
|
||||
|
||||
window.openSearch = function() {
|
||||
var modal = document.getElementById('search-modal');
|
||||
if (!modal) return;
|
||||
modal.classList.add('search-backdrop--open');
|
||||
var input = document.getElementById('search-input');
|
||||
if (input) {
|
||||
input.value = '';
|
||||
input.focus();
|
||||
// Trigger initial load (show recent tasks when empty)
|
||||
htmx.trigger(input, 'search');
|
||||
}
|
||||
searchSelectedIndex = 0;
|
||||
};
|
||||
|
||||
window.closeSearch = function() {
|
||||
var modal = document.getElementById('search-modal');
|
||||
if (modal) modal.classList.remove('search-backdrop--open');
|
||||
};
|
||||
|
||||
window.selectSearchResult = function(taskId) {
|
||||
closeSearch();
|
||||
// Load the task detail panel
|
||||
htmx.ajax('GET', '/board?handler=TaskDetail&id=' + taskId, {
|
||||
target: '#detail-panel',
|
||||
swap: 'innerHTML'
|
||||
});
|
||||
};
|
||||
|
||||
window.highlightResult = function(el) {
|
||||
var results = document.querySelectorAll('.search-result');
|
||||
results.forEach(function(r) { r.classList.remove('search-result--selected'); });
|
||||
el.classList.add('search-result--selected');
|
||||
// Update index
|
||||
searchSelectedIndex = Array.from(results).indexOf(el);
|
||||
};
|
||||
|
||||
// Keyboard navigation in search
|
||||
document.addEventListener('keydown', function(e) {
|
||||
var modal = document.getElementById('search-modal');
|
||||
if (!modal || !modal.classList.contains('search-backdrop--open')) {
|
||||
// Ctrl+K / Cmd+K to open
|
||||
if ((e.ctrlKey || e.metaKey) && e.key === 'k') {
|
||||
e.preventDefault();
|
||||
openSearch();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Modal is open
|
||||
if (e.key === 'Escape') {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
closeSearch();
|
||||
return;
|
||||
}
|
||||
|
||||
var results = document.querySelectorAll('.search-result');
|
||||
if (results.length === 0) return;
|
||||
|
||||
if (e.key === 'ArrowDown') {
|
||||
e.preventDefault();
|
||||
searchSelectedIndex = Math.min(searchSelectedIndex + 1, results.length - 1);
|
||||
updateSearchSelection(results);
|
||||
} else if (e.key === 'ArrowUp') {
|
||||
e.preventDefault();
|
||||
searchSelectedIndex = Math.max(searchSelectedIndex - 1, 0);
|
||||
updateSearchSelection(results);
|
||||
} else if (e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
var selected = results[searchSelectedIndex];
|
||||
if (selected) {
|
||||
var taskId = selected.dataset.taskId;
|
||||
selectSearchResult(taskId);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
function updateSearchSelection(results) {
|
||||
results.forEach(function(r, i) {
|
||||
if (i === searchSelectedIndex) {
|
||||
r.classList.add('search-result--selected');
|
||||
r.scrollIntoView({ block: 'nearest' });
|
||||
} else {
|
||||
r.classList.remove('search-result--selected');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Reset selection when search results are updated
|
||||
document.addEventListener('htmx:afterSwap', function(evt) {
|
||||
if (evt.detail.target && evt.detail.target.id === 'search-results') {
|
||||
searchSelectedIndex = 0;
|
||||
}
|
||||
});
|
||||
|
||||
})();
|
||||
|
||||
Reference in New Issue
Block a user