Polish terminal search UI with case/regex toggles and match status
Replaces the basic find bar with a polished overlay that matches the terminal-search CSS. Features: - Case sensitivity toggle (Aa button) - Regular expression toggle (.* button) - Match status indicator (found/no matches) - Smooth entrance/exit animation - Clears search decorations on close - Re-focuses existing bar if already open
This commit is contained in:
@@ -168,6 +168,19 @@
|
|||||||
color: var(--text-tertiary);
|
color: var(--text-tertiary);
|
||||||
padding: 0 var(--space-1);
|
padding: 0 var(--space-1);
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
min-width: 70px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.terminal-search-toggle {
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
font-size: var(--text-xs);
|
||||||
|
opacity: 0.5;
|
||||||
|
transition: opacity var(--transition-fast), color var(--transition-fast);
|
||||||
|
}
|
||||||
|
|
||||||
|
.terminal-search-toggle.is-active {
|
||||||
|
opacity: 1;
|
||||||
|
color: var(--accent);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* -----------------------------------------------------------------------
|
/* -----------------------------------------------------------------------
|
||||||
|
|||||||
+95
-22
@@ -577,47 +577,120 @@ class App {
|
|||||||
const sessionId = this.state.activeSessionId;
|
const sessionId = this.state.activeSessionId;
|
||||||
if (!sessionId) return;
|
if (!sessionId) return;
|
||||||
|
|
||||||
// Check if search bar already exists
|
|
||||||
const panel = document.getElementById(`panel-${sessionId}`);
|
const panel = document.getElementById(`panel-${sessionId}`);
|
||||||
if (!panel || panel.querySelector('.terminal-search-bar')) return;
|
if (!panel) return;
|
||||||
|
|
||||||
|
// If search bar already exists, just focus the input
|
||||||
|
const existing = panel.querySelector('.terminal-search');
|
||||||
|
if (existing) {
|
||||||
|
const input = existing.querySelector('input');
|
||||||
|
if (input) input.focus();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const bar = document.createElement('div');
|
const bar = document.createElement('div');
|
||||||
bar.className = 'terminal-search-bar';
|
bar.className = 'terminal-search';
|
||||||
bar.innerHTML = `
|
|
||||||
<input type="text" class="form-input terminal-search-input" placeholder="Find…" />
|
|
||||||
<button class="btn btn-xs btn-outline terminal-search-prev">▲</button>
|
|
||||||
<button class="btn btn-xs btn-outline terminal-search-next">▼</button>
|
|
||||||
<button class="btn btn-xs btn-outline terminal-search-close">✕</button>
|
|
||||||
`;
|
|
||||||
|
|
||||||
const input = bar.querySelector('.terminal-search-input');
|
const input = document.createElement('input');
|
||||||
const closeBtn = bar.querySelector('.terminal-search-close');
|
input.type = 'text';
|
||||||
const prevBtn = bar.querySelector('.terminal-search-prev');
|
input.placeholder = 'Find in terminal…';
|
||||||
const nextBtn = bar.querySelector('.terminal-search-next');
|
input.className = 'terminal-search-input';
|
||||||
|
|
||||||
const doSearch = () => this.terminals.search(sessionId, input.value);
|
const count = document.createElement('span');
|
||||||
const doPrev = () => this.terminals.searchPrevious(sessionId, input.value);
|
count.className = 'terminal-search-count';
|
||||||
|
count.textContent = '';
|
||||||
|
|
||||||
|
const prevBtn = document.createElement('button');
|
||||||
|
prevBtn.className = 'btn btn-xs btn-ghost';
|
||||||
|
prevBtn.textContent = '▲';
|
||||||
|
prevBtn.title = 'Previous match (Shift+Enter)';
|
||||||
|
|
||||||
|
const nextBtn = document.createElement('button');
|
||||||
|
nextBtn.className = 'btn btn-xs btn-ghost';
|
||||||
|
nextBtn.textContent = '▼';
|
||||||
|
nextBtn.title = 'Next match (Enter)';
|
||||||
|
|
||||||
|
const caseSensBtn = document.createElement('button');
|
||||||
|
caseSensBtn.className = 'btn btn-xs btn-ghost terminal-search-toggle';
|
||||||
|
caseSensBtn.textContent = 'Aa';
|
||||||
|
caseSensBtn.title = 'Toggle case sensitivity';
|
||||||
|
let caseSensitive = false;
|
||||||
|
|
||||||
|
const regexBtn = document.createElement('button');
|
||||||
|
regexBtn.className = 'btn btn-xs btn-ghost terminal-search-toggle';
|
||||||
|
regexBtn.textContent = '.*';
|
||||||
|
regexBtn.title = 'Toggle regular expression';
|
||||||
|
let useRegex = false;
|
||||||
|
|
||||||
|
const closeBtn = document.createElement('button');
|
||||||
|
closeBtn.className = 'btn btn-xs btn-ghost';
|
||||||
|
closeBtn.textContent = '✕';
|
||||||
|
closeBtn.title = 'Close (Escape)';
|
||||||
|
|
||||||
|
bar.appendChild(input);
|
||||||
|
bar.appendChild(count);
|
||||||
|
bar.appendChild(prevBtn);
|
||||||
|
bar.appendChild(nextBtn);
|
||||||
|
bar.appendChild(caseSensBtn);
|
||||||
|
bar.appendChild(regexBtn);
|
||||||
|
bar.appendChild(closeBtn);
|
||||||
|
|
||||||
|
const searchOpts = () => ({ caseSensitive, regex: useRegex });
|
||||||
|
|
||||||
|
const doSearch = () => {
|
||||||
|
const found = this.terminals.search(sessionId, input.value, searchOpts());
|
||||||
|
count.textContent = input.value ? (found ? 'Match found' : 'No matches') : '';
|
||||||
|
};
|
||||||
|
const doPrev = () => {
|
||||||
|
const found = this.terminals.searchPrevious(sessionId, input.value, searchOpts());
|
||||||
|
count.textContent = input.value ? (found ? 'Match found' : 'No matches') : '';
|
||||||
|
};
|
||||||
|
|
||||||
|
const closeSearch = () => {
|
||||||
|
// Clear search decorations
|
||||||
|
const entry = this.terminals.terminals.get(sessionId);
|
||||||
|
if (entry && entry.searchAddon) {
|
||||||
|
entry.searchAddon.clearDecorations();
|
||||||
|
}
|
||||||
|
bar.classList.remove('is-visible');
|
||||||
|
setTimeout(() => bar.remove(), 200);
|
||||||
|
this.terminals.focusTerminal(sessionId);
|
||||||
|
};
|
||||||
|
|
||||||
input.addEventListener('input', doSearch);
|
input.addEventListener('input', doSearch);
|
||||||
input.addEventListener('keydown', (e) => {
|
input.addEventListener('keydown', (e) => {
|
||||||
if (e.key === 'Enter') {
|
if (e.key === 'Enter') {
|
||||||
|
e.preventDefault();
|
||||||
e.shiftKey ? doPrev() : doSearch();
|
e.shiftKey ? doPrev() : doSearch();
|
||||||
}
|
}
|
||||||
if (e.key === 'Escape') {
|
if (e.key === 'Escape') {
|
||||||
bar.remove();
|
e.preventDefault();
|
||||||
this.terminals.focusTerminal(sessionId);
|
closeSearch();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
nextBtn.addEventListener('click', doSearch);
|
nextBtn.addEventListener('click', doSearch);
|
||||||
prevBtn.addEventListener('click', doPrev);
|
prevBtn.addEventListener('click', doPrev);
|
||||||
closeBtn.addEventListener('click', () => {
|
closeBtn.addEventListener('click', closeSearch);
|
||||||
bar.remove();
|
|
||||||
this.terminals.focusTerminal(sessionId);
|
caseSensBtn.addEventListener('click', () => {
|
||||||
|
caseSensitive = !caseSensitive;
|
||||||
|
caseSensBtn.classList.toggle('is-active', caseSensitive);
|
||||||
|
doSearch();
|
||||||
});
|
});
|
||||||
|
|
||||||
panel.prepend(bar);
|
regexBtn.addEventListener('click', () => {
|
||||||
input.focus();
|
useRegex = !useRegex;
|
||||||
|
regexBtn.classList.toggle('is-active', useRegex);
|
||||||
|
doSearch();
|
||||||
|
});
|
||||||
|
|
||||||
|
panel.appendChild(bar);
|
||||||
|
// Trigger the entrance animation
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
bar.classList.add('is-visible');
|
||||||
|
input.focus();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user