Palette: add browse filter bar for create file/folder, delete folder actions
This commit is contained in:
+70
-2
@@ -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 = `<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>
|
||||
<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-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');
|
||||
_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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user