diff --git a/frontend/js/palette.js b/frontend/js/palette.js
index d8f50eb..536bd03 100644
--- a/frontend/js/palette.js
+++ b/frontend/js/palette.js
@@ -17,6 +17,7 @@ function getCurrentFile() {
// ── DOM / state ──
let _overlay = null, _input = null, _results = null, _mode = 'files', _sel = 0, _to = null, _items = [];
let _browse = { action: null, vault: null, path: '' };
+let _browseFilterEl = null, _browseFilter = '', _allItems = [];
// ── Commands ──
const CMDS = [
@@ -36,6 +37,7 @@ const CMDS = [
// ── Browse: vault picker ──
function startBrowse(action) {
_browse = { action, vault: null, path: '' };
+ _browseFilter = ''; _allItems = [];
switchMode('browse-vault');
renderVaultPicker();
}
@@ -98,9 +100,57 @@ async function renderBrowse() {
if (_items.length === 0) _items.push({ type:'_empty', label: '📂 Dossier vide', sublabel: '' });
} catch { _items = [{ type:'_error', label: 'Erreur', sublabel: '' }]; }
+
+ // Store all items for filtering
+ _allItems = _items;
+ applyBrowseFilter();
+}
+
+// ── Browse filter ──
+function applyBrowseFilter() {
+ const q = _browseFilter.toLowerCase().trim();
+ const shouldKeep = (it) => {
+ if (!q) return true;
+ const label = (it.label || '').toLowerCase();
+ const sublabel = (it.sublabel || '').toLowerCase();
+ return label.includes(q) || sublabel.includes(q);
+ };
+ // Always keep action row and parent nav
+ _items = _allItems.filter(it => it.type === '_action' || it.type === '_nav' && it.label.includes('..') || shouldKeep(it));
render();
}
+function onBrowseFilterInput() {
+ _browseFilter = _browseFilterEl ? _browseFilterEl.value : '';
+ applyBrowseFilter();
+}
+
+function onBrowseFilterKey(e) {
+ if (e.key === 'Escape') {
+ e.preventDefault();
+ if (_browseFilter) {
+ // Clear filter
+ _browseFilter = '';
+ if (_browseFilterEl) _browseFilterEl.value = '';
+ applyBrowseFilter();
+ } else {
+ close();
+ }
+ } else if (e.key === 'ArrowDown') {
+ e.preventDefault();
+ const first = _results.querySelector('.cp-item');
+ if (first) { first.classList.add('cp-selected'); _sel = 0; first.focus ? first.focus() : _overlay.focus(); }
+ } else if (e.key === 'Enter') {
+ e.preventDefault();
+ if (_items.length > 0) exec(_sel);
+ } else if (e.key === 'Backspace' && !_browseFilter && !_browseFilterEl?.value) {
+ e.preventDefault();
+ const segs = _browse.path ? _browse.path.split('/') : [];
+ if (segs.length > 0) { _browse.path = segs.slice(0, -1).join('/'); renderBrowse(); }
+ else if (_browse.vault) { switchMode('browse-vault'); renderVaultPicker(); }
+ }
+}
+
// ── Name input modal ──
function askName() {
const label = _browse.action === 'create-file' ? 'Nom du fichier' : 'Nom du dossier';
@@ -199,13 +249,20 @@ function createDOM() {
_overlay.tabIndex = -1; // needed to receive keyboard events when input is hidden (browse mode)
_overlay.innerHTML = `
`;
+ `;
_input = _overlay.querySelector('.cp-input');
+ _browseFilterEl = _overlay.querySelector('#cp-browse-filter');
_results = _overlay.querySelector('.cp-results');
_overlay.addEventListener('click', (e) => { if (e.target === _overlay) close(); });
_input.addEventListener('keydown', onKeyDown);
_input.addEventListener('input', onInput);
+ // Browse filter events
+ if (_browseFilterEl) {
+ _browseFilterEl.addEventListener('input', onBrowseFilterInput);
+ _browseFilterEl.addEventListener('keydown', onBrowseFilterKey);
+ }
// Also listen on overlay for browse modes (input is hidden)
_overlay.addEventListener('keydown', onOverlayKeyDown);
document.body.appendChild(_overlay);
@@ -227,7 +284,10 @@ export function close() {
_overlay.classList.remove('active');
if (_to) clearTimeout(_to); _to = null;
_mode = 'files';
+ _browseFilter = '';
+ _allItems = [];
cleanupNameInput();
+ if (_browseFilterEl) { _browseFilterEl.style.display = 'none'; _browseFilterEl.value = ''; }
const bc = document.getElementById('cp-breadcrumb'); if (bc) bc.style.display = 'none';
const si = document.querySelector('.search-input'); if (si) si.focus();
}
@@ -239,9 +299,16 @@ function switchMode(m) {
if (m === 'browse' || m === 'browse-vault') {
_input.style.display = 'none'; _input.disabled = true;
_overlay.focus(); // overlay receives keyboard events when input is hidden
+ if (m === 'browse') {
+ // Show browse filter for directory navigation
+ if (_browseFilterEl) { _browseFilterEl.style.display = ''; _browseFilterEl.value = _browseFilter; _browseFilterEl.focus(); }
+ } else {
+ if (_browseFilterEl) _browseFilterEl.style.display = 'none';
+ }
} else {
_input.style.display = ''; _input.disabled = false;
_input.focus(); // restore focus to search input
+ if (_browseFilterEl) _browseFilterEl.style.display = 'none';
const bc = document.getElementById('cp-breadcrumb'); if (bc) bc.style.display = 'none';
}
}
@@ -312,8 +379,9 @@ function scroll() { const s = _results.querySelector('.cp-selected'); if (s) s.s
// ── Overlay keydown handler (for browse modes where input is hidden) ──
function onOverlayKeyDown(e) {
if (_mode !== 'browse' && _mode !== 'browse-vault') return;
- // Don't handle if name input is active (it has its own keydown handler)
+ // Don't handle if name input or browse filter is active (they have their own handlers)
if (document.getElementById('cp-name-input')) return;
+ if (document.activeElement === _browseFilterEl) return;
onKeyDown(e);
}
diff --git a/frontend/style.css b/frontend/style.css
index df333ed..0da9f35 100644
--- a/frontend/style.css
+++ b/frontend/style.css
@@ -6423,6 +6423,23 @@ body.popup-mode .content-area {
margin-top: 6px;
}
+/* Browse filter in command palette */
+.cp-browse-filter {
+ width: calc(100% - 24px);
+ margin: 8px 12px 4px;
+ padding: 7px 10px;
+ border: 1px solid var(--border);
+ border-radius: 6px;
+ background: var(--bg-primary);
+ color: var(--text-primary);
+ font-size: 0.8rem;
+ font-family: inherit;
+ outline: none;
+ transition: border-color 0.15s;
+}
+.cp-browse-filter:focus { border-color: var(--accent); }
+.cp-browse-filter::placeholder { color: var(--text-muted); }
+
/* ── Help Modal: Mobile responsive ── */
@media (max-width: 768px) {
.help-container {