- keybar: add Esc·Esc / ^O / ^T / ^B + per-button tooltips (KEY_MAP 14 keys)
- M1 search: @xterm/addon-search per terminal; 🔍 toolbar button + search box
(Enter=next, Shift+Enter=prev, Esc=close)
- M2 links: @xterm/addon-web-links per terminal (tap URLs Claude prints)
- tab bar split into #tabs (scrollable) + #toolbar (utility cluster) for future
utilities (QR/settings/dashboard)
- Browser-verified: 14 keybar buttons, search highlights matches, no console errors.
199 tests green, typechecks + build ok.
66 lines
1.9 KiB
TypeScript
66 lines
1.9 KiB
TypeScript
/**
|
||
* public/search.ts — scrollback search box for the active terminal (M1).
|
||
*
|
||
* Adds a 🔍 button to the toolbar that toggles a small search overlay.
|
||
* Enter = next, Shift+Enter = previous, Esc = close.
|
||
*/
|
||
|
||
export interface SearchHooks {
|
||
find: (query: string, dir: 'next' | 'prev') => void
|
||
clear: () => void
|
||
}
|
||
|
||
function makeBtn(label: string, title: string): HTMLButtonElement {
|
||
const b = document.createElement('button')
|
||
b.textContent = label
|
||
b.title = title
|
||
b.setAttribute('aria-label', title)
|
||
return b
|
||
}
|
||
|
||
export function mountSearch(toolbar: HTMLElement, hooks: SearchHooks): void {
|
||
const box = document.createElement('div')
|
||
box.id = 'searchbox'
|
||
box.style.display = 'none'
|
||
|
||
const input = document.createElement('input')
|
||
input.type = 'text'
|
||
input.className = 'search-input'
|
||
input.placeholder = 'Search scrollback…'
|
||
|
||
const prev = makeBtn('↑', 'Previous match (Shift+Enter)')
|
||
const next = makeBtn('↓', 'Next match (Enter)')
|
||
const close = makeBtn('×', 'Close (Esc)')
|
||
box.append(input, prev, next, close)
|
||
document.body.appendChild(box)
|
||
|
||
const open = (): void => {
|
||
box.style.display = 'flex'
|
||
input.focus()
|
||
input.select()
|
||
}
|
||
const hide = (): void => {
|
||
box.style.display = 'none'
|
||
hooks.clear()
|
||
}
|
||
|
||
input.addEventListener('keydown', (e) => {
|
||
if (e.key === 'Enter') {
|
||
e.preventDefault()
|
||
hooks.find(input.value, e.shiftKey ? 'prev' : 'next')
|
||
} else if (e.key === 'Escape') {
|
||
e.preventDefault()
|
||
hide()
|
||
}
|
||
e.stopPropagation()
|
||
})
|
||
prev.addEventListener('click', () => hooks.find(input.value, 'prev'))
|
||
next.addEventListener('click', () => hooks.find(input.value, 'next'))
|
||
close.addEventListener('click', hide)
|
||
|
||
const toggle = makeBtn('🔍', 'Search scrollback')
|
||
toggle.className = 'toolbtn'
|
||
toggle.addEventListener('click', () => (box.style.display === 'none' ? open() : hide()))
|
||
toolbar.appendChild(toggle)
|
||
}
|