feat(terminal): Add long-press repeat for Enter key and retain search queries (#11389)

Co-authored-by: Eugene <inbox@null.page>
This commit is contained in:
KaniPan
2026-09-12 11:51:12 +02:00
committed by GitHub
co-authored by Eugene
parent 202f3f5040
commit 7297babf84
3 changed files with 53 additions and 5 deletions
@@ -1,7 +1,8 @@
.terminal-toolbar-spacer
.content(#content, [style.opacity]='frontendIsReady ? 1 : 0')
search-panel(
*ngIf='showSearchPanel && hasFocus && frontend',
*ngIf='showSearchPanel && frontend',
[class.d-none]='!hasFocus',
@toolbarSlide,
[@.disabled]='!config.store.accessibility.animations',
[frontend]='frontend',
@@ -5,7 +5,9 @@
(ngModelChange)='onQueryChange()',
[class.text-danger]='state.resultCount == 0',
(click)='$event.stopPropagation()',
(keyup.enter)='findPrevious()',
(keydown.enter)='onKeyDown($event)',
(keyup.enter)='onKeyUp($event)',
(blur)='onBlur()',
(keyup.up)='findPrevious()',
(keyup.down)='findNext()',
(keyup.esc)='close.emit()',
@@ -1,4 +1,4 @@
import { Component, Input, Output, EventEmitter } from '@angular/core'
import { Component, Input, Output, EventEmitter, NgZone, OnDestroy } from '@angular/core'
import { Subject, debounceTime } from 'rxjs'
import { Frontend, SearchOptions, SearchState } from '../frontends/frontend'
import { ConfigService, NotificationsService, TranslateService } from 'tabby-core'
@@ -8,8 +8,8 @@ import { ConfigService, NotificationsService, TranslateService } from 'tabby-cor
templateUrl: './searchPanel.component.pug',
styleUrls: ['./searchPanel.component.scss'],
})
export class SearchPanelComponent {
@Input() query: string
export class SearchPanelComponent implements OnDestroy {
query = ''
@Input() frontend: Frontend
state: SearchState = { resultCount: 0 }
options: SearchOptions = {
@@ -20,6 +20,11 @@ export class SearchPanelComponent {
@Output() close = new EventEmitter()
private queryChanged = new Subject<string>()
private holdTimer: ReturnType<typeof setTimeout> | null = null
private repeatTimer: ReturnType<typeof setInterval> | null = null
private readonly HOLD_DELAY = 400
private readonly REPEAT_INTERVAL = 100
icons = {
'case': require('../icons/case.svg'),
@@ -34,6 +39,7 @@ export class SearchPanelComponent {
private notifications: NotificationsService,
private translate: TranslateService,
public config: ConfigService,
private ngZone: NgZone,
) {
this.queryChanged.pipe(debounceTime(250)).subscribe(() => {
this.findPrevious(true)
@@ -45,6 +51,44 @@ export class SearchPanelComponent {
this.queryChanged.next(this.query)
}
onKeyDown (event: KeyboardEvent): void {
if (event.key !== 'Enter' || event.repeat) { return }
if (this.holdTimer ?? this.repeatTimer) { return }
this.holdTimer = setTimeout(() => {
this.holdTimer = null
this.ngZone.runOutsideAngular(() => {
this.repeatTimer = setInterval(() => {
this.ngZone.run(() => {
this.findPrevious()
if (!this.state.resultCount) {
// nothing to cycle through - one notice is enough
this.stopRepeat()
}
})
}, this.REPEAT_INTERVAL)
})
}, this.HOLD_DELAY)
}
private stopRepeat (fireOnTap = false): void {
if (this.holdTimer) {
clearTimeout(this.holdTimer)
this.holdTimer = null
if (fireOnTap) { this.findPrevious() }
}
if (this.repeatTimer) { clearInterval(this.repeatTimer); this.repeatTimer = null }
}
onKeyUp (event: KeyboardEvent): void {
if (event.key !== 'Enter') { return }
this.stopRepeat(true)
}
onBlur (): void {
this.stopRepeat(false)
}
findNext (incremental = false): void {
if (!this.query) {
return
@@ -75,5 +119,6 @@ export class SearchPanelComponent {
ngOnDestroy (): void {
this.queryChanged.complete()
this.stopRepeat()
}
}