]>
Commit | Line | Data |
---|---|---|
6af662a5 RK |
1 | import { |
2 | Component, | |
3 | ViewChild, | |
4 | ElementRef, | |
5 | AfterViewInit, | |
6 | OnInit, | |
7 | OnDestroy, | |
8 | QueryList | |
9 | } from '@angular/core' | |
52cc0d54 | 10 | import { Router, NavigationEnd, Params, ActivatedRoute } from '@angular/router' |
9677fca7 | 11 | import { AuthService, ServerService } from '@app/core' |
f409f0c3 | 12 | import { I18n } from '@ngx-translate/i18n-polyfill' |
52cc0d54 | 13 | import { filter, first, tap, map } from 'rxjs/operators' |
6af662a5 | 14 | import { ListKeyManager } from '@angular/cdk/a11y' |
9677fca7 | 15 | import { UP_ARROW, DOWN_ARROW, ENTER } from '@angular/cdk/keycodes' |
52cc0d54 RK |
16 | import { SuggestionComponent, Result } from './suggestion.component' |
17 | import { of } from 'rxjs' | |
18 | import { getParameterByName } from '@app/shared/misc/utils' | |
9677fca7 | 19 | import { ServerConfig } from '@shared/models' |
f409f0c3 RK |
20 | |
21 | @Component({ | |
22 | selector: 'my-search-typeahead', | |
23 | templateUrl: './search-typeahead.component.html', | |
24 | styleUrls: [ './search-typeahead.component.scss' ] | |
25 | }) | |
6af662a5 | 26 | export class SearchTypeaheadComponent implements OnInit, OnDestroy, AfterViewInit { |
f409f0c3 | 27 | @ViewChild('contentWrapper', { static: true }) contentWrapper: ElementRef |
f409f0c3 RK |
28 | |
29 | hasChannel = false | |
30 | inChannel = false | |
6af662a5 | 31 | newSearch = true |
f409f0c3 RK |
32 | |
33 | searchInput: HTMLInputElement | |
9677fca7 | 34 | serverConfig: ServerConfig |
f409f0c3 RK |
35 | |
36 | URIPolicyText: string | |
37 | inAllText: string | |
38 | inThisChannelText: string | |
6af662a5 | 39 | globalSearchIndex = 'https://index.joinpeertube.org' |
f409f0c3 | 40 | |
6af662a5 | 41 | keyboardEventsManager: ListKeyManager<SuggestionComponent> |
f409f0c3 RK |
42 | results: any[] = [] |
43 | ||
44 | constructor ( | |
45 | private authService: AuthService, | |
46 | private router: Router, | |
52cc0d54 | 47 | private route: ActivatedRoute, |
9677fca7 | 48 | private serverService: ServerService, |
f409f0c3 RK |
49 | private i18n: I18n |
50 | ) { | |
6af662a5 | 51 | this.URIPolicyText = this.i18n('Determines whether you can resolve any distant content, or if your instance only allows doing so for instances it follows.') |
f409f0c3 RK |
52 | this.inAllText = this.i18n('In all PeerTube') |
53 | this.inThisChannelText = this.i18n('In this channel') | |
54 | } | |
55 | ||
56 | ngOnInit () { | |
57 | this.router.events | |
52cc0d54 | 58 | .pipe(filter(e => e instanceof NavigationEnd)) |
f409f0c3 RK |
59 | .subscribe((event: NavigationEnd) => { |
60 | this.hasChannel = event.url.startsWith('/videos/watch') | |
61 | this.inChannel = event.url.startsWith('/video-channels') | |
62 | this.computeResults() | |
63 | }) | |
52cc0d54 RK |
64 | |
65 | this.router.events | |
66 | .pipe( | |
67 | filter(e => e instanceof NavigationEnd), | |
68 | map(() => getParameterByName('search', window.location.href)) | |
69 | ) | |
70 | .subscribe(searchQuery => this.searchInput.value = searchQuery || '') | |
9677fca7 RK |
71 | |
72 | this.serverService.getConfig() | |
73 | .subscribe(config => this.serverConfig = config) | |
f409f0c3 RK |
74 | } |
75 | ||
6af662a5 RK |
76 | ngOnDestroy () { |
77 | if (this.keyboardEventsManager) this.keyboardEventsManager.change.unsubscribe() | |
78 | } | |
79 | ||
f409f0c3 RK |
80 | ngAfterViewInit () { |
81 | this.searchInput = this.contentWrapper.nativeElement.childNodes[0] | |
82 | this.searchInput.addEventListener('input', this.computeResults.bind(this)) | |
6af662a5 | 83 | this.searchInput.addEventListener('keyup', this.handleKeyUp.bind(this)) |
f409f0c3 RK |
84 | } |
85 | ||
86 | get hasSearch () { | |
87 | return !!this.searchInput && !!this.searchInput.value | |
88 | } | |
89 | ||
6af662a5 RK |
90 | get activeResult () { |
91 | return this.keyboardEventsManager && this.keyboardEventsManager.activeItem && this.keyboardEventsManager.activeItem.result | |
92 | } | |
93 | ||
94 | get showHelp () { | |
95 | return this.hasSearch && this.newSearch && this.activeResult && this.activeResult.type === 'search-global' || false | |
96 | } | |
97 | ||
9677fca7 RK |
98 | get URIPolicy (): 'only-followed' | 'any' { |
99 | return ( | |
100 | this.authService.isLoggedIn() | |
101 | ? this.serverConfig.search.remoteUri.users | |
102 | : this.serverConfig.search.remoteUri.anonymous | |
103 | ) | |
104 | ? 'any' | |
105 | : 'only-followed' | |
106 | } | |
107 | ||
f409f0c3 | 108 | computeResults () { |
6af662a5 | 109 | this.newSearch = true |
52cc0d54 | 110 | let results: Result[] = [] |
f409f0c3 RK |
111 | |
112 | if (this.hasSearch) { | |
113 | results = [ | |
52cc0d54 | 114 | /* Channel search is still unimplemented. Uncomment when it is. |
f409f0c3 RK |
115 | { |
116 | text: this.searchInput.value, | |
117 | type: 'search-channel' | |
118 | }, | |
52cc0d54 | 119 | */ |
6af662a5 RK |
120 | { |
121 | text: this.searchInput.value, | |
52cc0d54 RK |
122 | type: 'search-instance', |
123 | default: true | |
6af662a5 | 124 | }, |
52cc0d54 | 125 | /* Global search is still unimplemented. Uncomment when it is. |
f409f0c3 RK |
126 | { |
127 | text: this.searchInput.value, | |
128 | type: 'search-global' | |
129 | }, | |
52cc0d54 | 130 | */ |
f409f0c3 RK |
131 | ...results |
132 | ] | |
133 | } | |
134 | ||
135 | this.results = results.filter( | |
52cc0d54 | 136 | (result: Result) => { |
f409f0c3 RK |
137 | // if we're not in a channel or one of its videos/playlits, show all channel-related results |
138 | if (!(this.hasChannel || this.inChannel)) return !result.type.includes('channel') | |
139 | // if we're in a channel, show all channel-related results except for the channel redirection itself | |
140 | if (this.inChannel) return !(result.type === 'channel') | |
141 | return true | |
142 | } | |
143 | ) | |
144 | } | |
145 | ||
52cc0d54 RK |
146 | setEventItems (event: { items: QueryList<SuggestionComponent>, index?: number }) { |
147 | event.items.forEach(e => { | |
148 | if (this.keyboardEventsManager.activeItem && this.keyboardEventsManager.activeItem === e) { | |
149 | this.keyboardEventsManager.activeItem.active = true | |
150 | } else { | |
151 | e.active = false | |
152 | } | |
153 | }) | |
154 | } | |
155 | ||
6af662a5 RK |
156 | initKeyboardEventsManager (event: { items: QueryList<SuggestionComponent>, index?: number }) { |
157 | if (this.keyboardEventsManager) this.keyboardEventsManager.change.unsubscribe() | |
158 | this.keyboardEventsManager = new ListKeyManager(event.items) | |
159 | if (event.index !== undefined) { | |
160 | this.keyboardEventsManager.setActiveItem(event.index) | |
52cc0d54 RK |
161 | } else { |
162 | this.keyboardEventsManager.setFirstItemActive() | |
6af662a5 RK |
163 | } |
164 | this.keyboardEventsManager.change.subscribe( | |
52cc0d54 | 165 | _ => this.setEventItems(event) |
6af662a5 RK |
166 | ) |
167 | } | |
168 | ||
6af662a5 | 169 | handleKeyUp (event: KeyboardEvent, indexSelected?: number) { |
f409f0c3 RK |
170 | event.stopImmediatePropagation() |
171 | if (this.keyboardEventsManager) { | |
52cc0d54 | 172 | if (event.keyCode === DOWN_ARROW || event.keyCode === UP_ARROW) { |
f409f0c3 RK |
173 | this.keyboardEventsManager.onKeydown(event) |
174 | return false | |
175 | } else if (event.keyCode === ENTER) { | |
6af662a5 | 176 | this.newSearch = false |
52cc0d54 | 177 | this.doSearch() |
f409f0c3 RK |
178 | return false |
179 | } | |
180 | } | |
181 | } | |
52cc0d54 RK |
182 | |
183 | doSearch () { | |
184 | const queryParams: Params = {} | |
185 | ||
186 | if (window.location.pathname === '/search' && this.route.snapshot.queryParams) { | |
187 | Object.assign(queryParams, this.route.snapshot.queryParams) | |
188 | } | |
189 | ||
190 | Object.assign(queryParams, { search: this.searchInput.value }) | |
191 | ||
192 | const o = this.authService.isLoggedIn() | |
193 | ? this.loadUserLanguagesIfNeeded(queryParams) | |
194 | : of(true) | |
195 | ||
196 | o.subscribe(() => this.router.navigate([ '/search' ], { queryParams })) | |
197 | } | |
198 | ||
199 | private loadUserLanguagesIfNeeded (queryParams: any) { | |
200 | if (queryParams && queryParams.languageOneOf) return of(queryParams) | |
201 | ||
202 | return this.authService.userInformationLoaded | |
203 | .pipe( | |
204 | first(), | |
205 | tap(() => Object.assign(queryParams, { languageOneOf: this.authService.getUser().videoLanguages })) | |
206 | ) | |
207 | } | |
f409f0c3 | 208 | } |