feat(v0.3): keybar Claude shortcuts + scrollback search + clickable links
- 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.
This commit is contained in:
65
public/search.ts
Normal file
65
public/search.ts
Normal file
@@ -0,0 +1,65 @@
|
||||
/**
|
||||
* 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)
|
||||
}
|
||||
Reference in New Issue
Block a user