]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blame - client/src/app/header/search-typeahead.component.ts
Translated using Weblate (German)
[github/Chocobozzz/PeerTube.git] / client / src / app / header / search-typeahead.component.ts
CommitLineData
5fb2e288 1import { of } from 'rxjs'
67ed6552 2import { first, tap } from 'rxjs/operators'
5fb2e288 3import { ListKeyManager } from '@angular/cdk/a11y'
2989628b 4import { AfterViewChecked, Component, OnDestroy, OnInit, QueryList, ViewChildren } from '@angular/core'
4c1c1709 5import { ActivatedRoute, Params, Router } from '@angular/router'
9677fca7 6import { AuthService, ServerService } from '@app/core'
2989628b 7import { HTMLServerConfig, SearchTargetType } from '@shared/models'
5fb2e288 8import { SuggestionComponent, SuggestionPayload, SuggestionPayloadType } from './suggestion.component'
f409f0c3
RK
9
10@Component({
11 selector: 'my-search-typeahead',
12 templateUrl: './search-typeahead.component.html',
13 styleUrls: [ './search-typeahead.component.scss' ]
14})
2989628b 15export class SearchTypeaheadComponent implements OnInit, AfterViewChecked, OnDestroy {
5fb2e288 16 @ViewChildren(SuggestionComponent) suggestionItems: QueryList<SuggestionComponent>
f409f0c3
RK
17
18 hasChannel = false
19 inChannel = false
5fb2e288 20 areSuggestionsOpened = true
f409f0c3 21
9b8a7aa8 22 search = ''
2989628b 23 serverConfig: HTMLServerConfig
f409f0c3 24
f409f0c3
RK
25 inThisChannelText: string
26
6af662a5 27 keyboardEventsManager: ListKeyManager<SuggestionComponent>
5fb2e288
C
28 results: SuggestionPayload[] = []
29
30 activeSearch: SuggestionPayloadType
31
32 private scheduleKeyboardEventsInit = false
f409f0c3
RK
33
34 constructor (
35 private authService: AuthService,
36 private router: Router,
52cc0d54 37 private route: ActivatedRoute,
9b8a7aa8
RK
38 private serverService: ServerService
39 ) {}
f409f0c3
RK
40
41 ngOnInit () {
71e75ef2 42 this.route.queryParams
36004aa7 43 .pipe(first(params => this.isOnSearch() && params.search !== undefined && params.search !== null))
71e75ef2 44 .subscribe(params => this.search = params.search)
5fb2e288 45
2989628b
C
46 this.serverConfig = this.serverService.getHTMLConfig()
47 this.computeTypeahead()
48
49 this.serverService.configReloaded
5fb2e288
C
50 .subscribe(config => {
51 this.serverConfig = config
5fb2e288 52 this.computeTypeahead()
5fb2e288 53 })
f409f0c3
RK
54 }
55
5fb2e288
C
56 ngAfterViewChecked () {
57 if (this.scheduleKeyboardEventsInit && !this.keyboardEventsManager) {
58 // Avoid ExpressionChangedAfterItHasBeenCheckedError errors
59 setTimeout(() => this.initKeyboardEventsManager(), 0)
60 }
6af662a5
RK
61 }
62
5fb2e288
C
63 ngOnDestroy () {
64 if (this.keyboardEventsManager) this.keyboardEventsManager.change.unsubscribe()
6af662a5
RK
65 }
66
5fb2e288 67 areInstructionsDisplayed () {
9b8a7aa8
RK
68 return !this.search
69 }
70
5fb2e288
C
71 showSearchGlobalHelp () {
72 return this.search && this.areSuggestionsOpened && this.keyboardEventsManager?.activeItem?.result?.type === 'search-index'
6af662a5
RK
73 }
74
5fb2e288 75 canSearchAnyURI () {
9b8a7aa8 76 if (!this.serverConfig) return false
5fb2e288 77
9b8a7aa8
RK
78 return this.authService.isLoggedIn()
79 ? this.serverConfig.search.remoteUri.users
80 : this.serverConfig.search.remoteUri.anonymous
81 }
82
83 onSearchChange () {
5fb2e288
C
84 this.computeTypeahead()
85 }
86
87 initKeyboardEventsManager () {
88 if (this.keyboardEventsManager) return
89
90 this.keyboardEventsManager = new ListKeyManager(this.suggestionItems)
91
92 const activeIndex = this.suggestionItems.toArray().findIndex(i => i.result.default === true)
93 if (activeIndex === -1) {
94 console.error('Cannot find active index.', { suggestionItems: this.suggestionItems })
f409f0c3
RK
95 }
96
5fb2e288
C
97 this.updateItemsState(activeIndex)
98
99 this.keyboardEventsManager.change.subscribe(
100 _ => this.updateItemsState()
f409f0c3
RK
101 )
102 }
103
5fb2e288
C
104 computeTypeahead () {
105 const searchIndexConfig = this.serverConfig.search.searchIndex
106
107 if (!this.activeSearch) {
3b0bd70a 108 if (searchIndexConfig.enabled && (searchIndexConfig.isDefaultSearch || searchIndexConfig.disableLocalSearch)) {
5fb2e288 109 this.activeSearch = 'search-index'
3b0bd70a
C
110 } else {
111 this.activeSearch = 'search-instance'
52cc0d54 112 }
5fb2e288
C
113 }
114
115 this.areSuggestionsOpened = true
116 this.results = []
117
118 if (!this.search) return
119
120 if (searchIndexConfig.enabled === false || searchIndexConfig.disableLocalSearch !== true) {
121 this.results.push({
122 text: this.search,
123 type: 'search-instance',
124 default: this.activeSearch === 'search-instance'
125 })
126 }
127
128 if (searchIndexConfig.enabled) {
129 this.results.push({
130 text: this.search,
131 type: 'search-index',
132 default: this.activeSearch === 'search-index'
133 })
134 }
135
136 this.scheduleKeyboardEventsInit = true
52cc0d54
RK
137 }
138
5fb2e288
C
139 updateItemsState (index?: number) {
140 if (index !== undefined) {
141 this.keyboardEventsManager.setActiveItem(index)
142 }
8a979d73 143
5fb2e288
C
144 for (const item of this.suggestionItems) {
145 if (this.keyboardEventsManager.activeItem && this.keyboardEventsManager.activeItem === item) {
146 item.active = true
147 this.activeSearch = item.result.type
148 continue
149 }
8a979d73 150
5fb2e288 151 item.active = false
6af662a5 152 }
5fb2e288 153 }
8a979d73 154
5bd427e0 155 onSuggestionClicked (payload: SuggestionPayload) {
5fb2e288
C
156 this.doSearch(this.buildSearchTarget(payload))
157 }
158
159 onSuggestionHover (index: number) {
160 this.updateItemsState(index)
6af662a5
RK
161 }
162
be6343d2 163 handleKey (event: KeyboardEvent) {
8a979d73 164 if (!this.keyboardEventsManager) return
4c1c1709 165
8a979d73 166 switch (event.key) {
4c1c1709
C
167 case 'ArrowDown':
168 case 'ArrowUp':
5fb2e288
C
169 event.stopPropagation()
170
f409f0c3 171 this.keyboardEventsManager.onKeydown(event)
8a979d73 172 break
5bd427e0
C
173
174 case 'Enter':
175 event.stopPropagation()
176 this.doSearch()
177 break
f409f0c3
RK
178 }
179 }
52cc0d54 180
36004aa7
RK
181 isOnSearch () {
182 return window.location.pathname === '/search'
183 }
184
5fb2e288
C
185 doSearch (searchTarget?: SearchTargetType) {
186 this.areSuggestionsOpened = false
52cc0d54
RK
187 const queryParams: Params = {}
188
36004aa7 189 if (this.isOnSearch() && this.route.snapshot.queryParams) {
52cc0d54
RK
190 Object.assign(queryParams, this.route.snapshot.queryParams)
191 }
192
5fb2e288
C
193 if (!searchTarget) {
194 searchTarget = this.buildSearchTarget(this.keyboardEventsManager.activeItem.result)
195 }
196
197 Object.assign(queryParams, { search: this.search, searchTarget })
52cc0d54
RK
198
199 const o = this.authService.isLoggedIn()
200 ? this.loadUserLanguagesIfNeeded(queryParams)
201 : of(true)
202
203 o.subscribe(() => this.router.navigate([ '/search' ], { queryParams }))
204 }
205
206 private loadUserLanguagesIfNeeded (queryParams: any) {
9df52d66 207 if (queryParams?.languageOneOf) return of(queryParams)
52cc0d54
RK
208
209 return this.authService.userInformationLoaded
210 .pipe(
211 first(),
212 tap(() => Object.assign(queryParams, { languageOneOf: this.authService.getUser().videoLanguages }))
213 )
214 }
5fb2e288
C
215
216 private buildSearchTarget (result: SuggestionPayload): SearchTargetType {
217 if (result.type === 'search-index') {
218 return 'search-index'
219 }
220
221 return 'local'
222 }
f409f0c3 223}