aboutsummaryrefslogtreecommitdiffhomepage
path: root/client/src/app/header/search-typeahead.component.ts
diff options
context:
space:
mode:
authorRigel Kent <sendmemail@rigelk.eu>2020-01-25 16:32:06 +0100
committerRigel Kent <sendmemail@rigelk.eu>2020-02-13 16:32:21 +0100
commit6af662a5961b48ac12682df2b8b971060a2cc67d (patch)
treede1efc71cbe8543b5b832e5de99a407a54c37220 /client/src/app/header/search-typeahead.component.ts
parentf409f0c3b91d85c66b4841d72ea65b5fbe1483d8 (diff)
downloadPeerTube-6af662a5961b48ac12682df2b8b971060a2cc67d.tar.gz
PeerTube-6af662a5961b48ac12682df2b8b971060a2cc67d.tar.zst
PeerTube-6af662a5961b48ac12682df2b8b971060a2cc67d.zip
Add keyboard navigation and hepler to typeahead
Diffstat (limited to 'client/src/app/header/search-typeahead.component.ts')
-rw-r--r--client/src/app/header/search-typeahead.component.ts70
1 files changed, 58 insertions, 12 deletions
diff --git a/client/src/app/header/search-typeahead.component.ts b/client/src/app/header/search-typeahead.component.ts
index d12a9682e..084bdd58b 100644
--- a/client/src/app/header/search-typeahead.component.ts
+++ b/client/src/app/header/search-typeahead.component.ts
@@ -1,23 +1,31 @@
1import { Component, ViewChild, ElementRef, AfterViewInit, OnInit } from '@angular/core' 1import {
2 Component,
3 ViewChild,
4 ElementRef,
5 AfterViewInit,
6 OnInit,
7 OnDestroy,
8 QueryList
9} from '@angular/core'
2import { Router, NavigationEnd } from '@angular/router' 10import { Router, NavigationEnd } from '@angular/router'
3import { AuthService } from '@app/core' 11import { AuthService } from '@app/core'
4import { I18n } from '@ngx-translate/i18n-polyfill' 12import { I18n } from '@ngx-translate/i18n-polyfill'
5import { filter } from 'rxjs/operators' 13import { filter } from 'rxjs/operators'
6import { ListKeyManager, ListKeyManagerOption } from '@angular/cdk/a11y' 14import { ListKeyManager } from '@angular/cdk/a11y'
7import { UP_ARROW, DOWN_ARROW, ENTER } from '@angular/cdk/keycodes' 15import { UP_ARROW, DOWN_ARROW, ENTER, TAB } from '@angular/cdk/keycodes'
16import { SuggestionComponent } from './suggestion.component'
8 17
9@Component({ 18@Component({
10 selector: 'my-search-typeahead', 19 selector: 'my-search-typeahead',
11 templateUrl: './search-typeahead.component.html', 20 templateUrl: './search-typeahead.component.html',
12 styleUrls: [ './search-typeahead.component.scss' ] 21 styleUrls: [ './search-typeahead.component.scss' ]
13}) 22})
14export class SearchTypeaheadComponent implements OnInit, AfterViewInit { 23export class SearchTypeaheadComponent implements OnInit, OnDestroy, AfterViewInit {
15 @ViewChild('contentWrapper', { static: true }) contentWrapper: ElementRef 24 @ViewChild('contentWrapper', { static: true }) contentWrapper: ElementRef
16 @ViewChild('optionsList', { static: true }) optionsList: ElementRef
17 25
18 hasChannel = false 26 hasChannel = false
19 inChannel = false 27 inChannel = false
20 keyboardEventsManager: ListKeyManager<ListKeyManagerOption> 28 newSearch = true
21 29
22 searchInput: HTMLInputElement 30 searchInput: HTMLInputElement
23 URIPolicy: 'only-followed' | 'any' = 'any' 31 URIPolicy: 'only-followed' | 'any' = 'any'
@@ -25,7 +33,9 @@ export class SearchTypeaheadComponent implements OnInit, AfterViewInit {
25 URIPolicyText: string 33 URIPolicyText: string
26 inAllText: string 34 inAllText: string
27 inThisChannelText: string 35 inThisChannelText: string
36 globalSearchIndex = 'https://index.joinpeertube.org'
28 37
38 keyboardEventsManager: ListKeyManager<SuggestionComponent>
29 results: any[] = [] 39 results: any[] = []
30 40
31 constructor ( 41 constructor (
@@ -33,7 +43,7 @@ export class SearchTypeaheadComponent implements OnInit, AfterViewInit {
33 private router: Router, 43 private router: Router,
34 private i18n: I18n 44 private i18n: I18n
35 ) { 45 ) {
36 this.URIPolicyText = this.i18n('Determines whether you can resolve any distant content from its URL, or if your instance only allows doing so for instances it follows.') 46 this.URIPolicyText = this.i18n('Determines whether you can resolve any distant content, or if your instance only allows doing so for instances it follows.')
37 this.inAllText = this.i18n('In all PeerTube') 47 this.inAllText = this.i18n('In all PeerTube')
38 this.inThisChannelText = this.i18n('In this channel') 48 this.inThisChannelText = this.i18n('In this channel')
39 } 49 }
@@ -48,16 +58,30 @@ export class SearchTypeaheadComponent implements OnInit, AfterViewInit {
48 }) 58 })
49 } 59 }
50 60
61 ngOnDestroy () {
62 if (this.keyboardEventsManager) this.keyboardEventsManager.change.unsubscribe()
63 }
64
51 ngAfterViewInit () { 65 ngAfterViewInit () {
52 this.searchInput = this.contentWrapper.nativeElement.childNodes[0] 66 this.searchInput = this.contentWrapper.nativeElement.childNodes[0]
53 this.searchInput.addEventListener('input', this.computeResults.bind(this)) 67 this.searchInput.addEventListener('input', this.computeResults.bind(this))
68 this.searchInput.addEventListener('keyup', this.handleKeyUp.bind(this))
54 } 69 }
55 70
56 get hasSearch () { 71 get hasSearch () {
57 return !!this.searchInput && !!this.searchInput.value 72 return !!this.searchInput && !!this.searchInput.value
58 } 73 }
59 74
75 get activeResult () {
76 return this.keyboardEventsManager && this.keyboardEventsManager.activeItem && this.keyboardEventsManager.activeItem.result
77 }
78
79 get showHelp () {
80 return this.hasSearch && this.newSearch && this.activeResult && this.activeResult.type === 'search-global' || false
81 }
82
60 computeResults () { 83 computeResults () {
84 this.newSearch = true
61 let results = [ 85 let results = [
62 { 86 {
63 text: 'Maître poney', 87 text: 'Maître poney',
@@ -73,6 +97,10 @@ export class SearchTypeaheadComponent implements OnInit, AfterViewInit {
73 }, 97 },
74 { 98 {
75 text: this.searchInput.value, 99 text: this.searchInput.value,
100 type: 'search-instance'
101 },
102 {
103 text: this.searchInput.value,
76 type: 'search-global' 104 type: 'search-global'
77 }, 105 },
78 ...results 106 ...results
@@ -90,20 +118,38 @@ export class SearchTypeaheadComponent implements OnInit, AfterViewInit {
90 ) 118 )
91 } 119 }
92 120
121 initKeyboardEventsManager (event: { items: QueryList<SuggestionComponent>, index?: number }) {
122 if (this.keyboardEventsManager) this.keyboardEventsManager.change.unsubscribe()
123 this.keyboardEventsManager = new ListKeyManager(event.items)
124 if (event.index !== undefined) {
125 this.keyboardEventsManager.setActiveItem(event.index)
126 event.items.forEach(e => e.active = false)
127 this.keyboardEventsManager.activeItem.active = true
128 }
129 this.keyboardEventsManager.change.subscribe(
130 val => {
131 event.items.forEach(e => e.active = false)
132 this.keyboardEventsManager.activeItem.active = true
133 }
134 )
135 }
136
93 isUserLoggedIn () { 137 isUserLoggedIn () {
94 return this.authService.isLoggedIn() 138 return this.authService.isLoggedIn()
95 } 139 }
96 140
97 handleKeyUp (event: KeyboardEvent) { 141 handleKeyUp (event: KeyboardEvent, indexSelected?: number) {
98 event.stopImmediatePropagation() 142 event.stopImmediatePropagation()
99 if (this.keyboardEventsManager) { 143 if (this.keyboardEventsManager) {
100 if (event.keyCode === DOWN_ARROW || event.keyCode === UP_ARROW) { 144 if (event.keyCode === TAB) {
101 // passing the event to key manager so we get a change fired 145 this.keyboardEventsManager.setNextItemActive()
146 return false
147 } else if (event.keyCode === DOWN_ARROW || event.keyCode === UP_ARROW) {
102 this.keyboardEventsManager.onKeydown(event) 148 this.keyboardEventsManager.onKeydown(event)
103 return false 149 return false
104 } else if (event.keyCode === ENTER) { 150 } else if (event.keyCode === ENTER) {
105 // when we hit enter, the keyboardManager should call the selectItem method of the `ListItemComponent` 151 this.newSearch = false
106 // this.keyboardEventsManager.activeItem 152 // this.router.navigate(this.keyboardEventsManager.activeItem.result)
107 return false 153 return false
108 } 154 }
109 } 155 }