Palette: add browse filter bar for create file/folder, delete folder actions
CI / lint (push) Failing after 4s
CI / test (push) Has been skipped
CI / build (push) Has been skipped
CI / security (push) Successful in 9s

This commit is contained in:
2026-06-03 10:26:07 -04:00
parent b513add15b
commit 81e53a930a
2 changed files with 87 additions and 2 deletions
+70 -2
View File
@@ -17,6 +17,7 @@ function getCurrentFile() {
// ── DOM / state ── // ── DOM / state ──
let _overlay = null, _input = null, _results = null, _mode = 'files', _sel = 0, _to = null, _items = []; let _overlay = null, _input = null, _results = null, _mode = 'files', _sel = 0, _to = null, _items = [];
let _browse = { action: null, vault: null, path: '' }; let _browse = { action: null, vault: null, path: '' };
let _browseFilterEl = null, _browseFilter = '', _allItems = [];
// ── Commands ── // ── Commands ──
const CMDS = [ const CMDS = [
@@ -36,6 +37,7 @@ const CMDS = [
// ── Browse: vault picker ── // ── Browse: vault picker ──
function startBrowse(action) { function startBrowse(action) {
_browse = { action, vault: null, path: '' }; _browse = { action, vault: null, path: '' };
_browseFilter = ''; _allItems = [];
switchMode('browse-vault'); switchMode('browse-vault');
renderVaultPicker(); renderVaultPicker();
} }
@@ -98,9 +100,57 @@ async function renderBrowse() {
if (_items.length === 0) _items.push({ type:'_empty', label: '📂 Dossier vide', sublabel: '' }); if (_items.length === 0) _items.push({ type:'_empty', label: '📂 Dossier vide', sublabel: '' });
} catch { _items = [{ type:'_error', label: 'Erreur', 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(); 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 ── // ── Name input modal ──
function askName() { function askName() {
const label = _browse.action === 'create-file' ? 'Nom du fichier' : 'Nom du dossier'; 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.tabIndex = -1; // needed to receive keyboard events when input is hidden (browse mode)
_overlay.innerHTML = `<div class="command-palette"> _overlay.innerHTML = `<div class="command-palette">
<div class="cp-header"><input type="text" class="cp-input" placeholder="Rechercher un fichier... (tapez > pour les commandes)" spellcheck="false" autocomplete="off"><div class="cp-breadcrumb" id="cp-breadcrumb"></div><span class="cp-hint">Ctrl+Shift+Espace</span></div> <div class="cp-header"><input type="text" class="cp-input" placeholder="Rechercher un fichier... (tapez > pour les commandes)" spellcheck="false" autocomplete="off"><div class="cp-breadcrumb" id="cp-breadcrumb"></div><span class="cp-hint">Ctrl+Shift+Espace</span></div>
<input type="text" class="cp-browse-filter" id="cp-browse-filter" placeholder="Filtrer dossiers/fichiers..." spellcheck="false" autocomplete="off" style="display:none">
<div class="cp-results"></div> <div class="cp-results"></div>
<div class="cp-footer"><span>↑↓ naviguer</span><span>↵ ouvrir/entrer</span><span>⌫ monter</span><span>Esc fermer</span></div></div>`; <div class="cp-footer"><span id="cp-footer-hint">↑↓ naviguer</span><span>↵ ouvrir/entrer</span><span>⌫ monter</span><span>Esc fermer</span></div></div>`;
_input = _overlay.querySelector('.cp-input'); _input = _overlay.querySelector('.cp-input');
_browseFilterEl = _overlay.querySelector('#cp-browse-filter');
_results = _overlay.querySelector('.cp-results'); _results = _overlay.querySelector('.cp-results');
_overlay.addEventListener('click', (e) => { if (e.target === _overlay) close(); }); _overlay.addEventListener('click', (e) => { if (e.target === _overlay) close(); });
_input.addEventListener('keydown', onKeyDown); _input.addEventListener('keydown', onKeyDown);
_input.addEventListener('input', onInput); _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) // Also listen on overlay for browse modes (input is hidden)
_overlay.addEventListener('keydown', onOverlayKeyDown); _overlay.addEventListener('keydown', onOverlayKeyDown);
document.body.appendChild(_overlay); document.body.appendChild(_overlay);
@@ -227,7 +284,10 @@ export function close() {
_overlay.classList.remove('active'); _overlay.classList.remove('active');
if (_to) clearTimeout(_to); _to = null; if (_to) clearTimeout(_to); _to = null;
_mode = 'files'; _mode = 'files';
_browseFilter = '';
_allItems = [];
cleanupNameInput(); cleanupNameInput();
if (_browseFilterEl) { _browseFilterEl.style.display = 'none'; _browseFilterEl.value = ''; }
const bc = document.getElementById('cp-breadcrumb'); if (bc) bc.style.display = 'none'; const bc = document.getElementById('cp-breadcrumb'); if (bc) bc.style.display = 'none';
const si = document.querySelector('.search-input'); if (si) si.focus(); const si = document.querySelector('.search-input'); if (si) si.focus();
} }
@@ -239,9 +299,16 @@ function switchMode(m) {
if (m === 'browse' || m === 'browse-vault') { if (m === 'browse' || m === 'browse-vault') {
_input.style.display = 'none'; _input.disabled = true; _input.style.display = 'none'; _input.disabled = true;
_overlay.focus(); // overlay receives keyboard events when input is hidden _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 { } else {
_input.style.display = ''; _input.disabled = false; _input.style.display = ''; _input.disabled = false;
_input.focus(); // restore focus to search input _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'; 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) ── // ── Overlay keydown handler (for browse modes where input is hidden) ──
function onOverlayKeyDown(e) { function onOverlayKeyDown(e) {
if (_mode !== 'browse' && _mode !== 'browse-vault') return; 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.getElementById('cp-name-input')) return;
if (document.activeElement === _browseFilterEl) return;
onKeyDown(e); onKeyDown(e);
} }
+17
View File
@@ -6423,6 +6423,23 @@ body.popup-mode .content-area {
margin-top: 6px; 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 ── */ /* ── Help Modal: Mobile responsive ── */
@media (max-width: 768px) { @media (max-width: 768px) {
.help-container { .help-container {