X-Git-Url: https://git.immae.eu/?a=blobdiff_plain;f=client%2Fsrc%2Fapp%2Fheader%2Fsearch-typeahead.component.ts;h=0794ec8f4774e7f3d71b70cf1bd7d593927b2786;hb=601b661315491ecde1002c5a08e7fdf75dfab574;hp=d0350368dab790bd80ff089e1b84a32ae0b101e4;hpb=be6343d26ec07fd792de069229bd3be27e72d129;p=github%2FChocobozzz%2FPeerTube.git diff --git a/client/src/app/header/search-typeahead.component.ts b/client/src/app/header/search-typeahead.component.ts index d0350368d..0794ec8f4 100644 --- a/client/src/app/header/search-typeahead.component.ts +++ b/client/src/app/header/search-typeahead.component.ts @@ -1,31 +1,35 @@ -import { Component, ElementRef, OnDestroy, OnInit, QueryList, ViewChild } from '@angular/core' -import { ActivatedRoute, Params, Router } from '@angular/router' -import { AuthService, ServerService } from '@app/core' +import { of } from 'rxjs' import { first, tap } from 'rxjs/operators' import { ListKeyManager } from '@angular/cdk/a11y' -import { Result, SuggestionComponent } from './suggestion.component' -import { of } from 'rxjs' -import { ServerConfig } from '@shared/models' +import { AfterViewChecked, Component, OnDestroy, OnInit, QueryList, ViewChildren } from '@angular/core' +import { ActivatedRoute, Params, Router } from '@angular/router' +import { AuthService, ServerService } from '@app/core' +import { HTMLServerConfig, SearchTargetType } from '@shared/models' +import { SuggestionComponent, SuggestionPayload, SuggestionPayloadType } from './suggestion.component' @Component({ selector: 'my-search-typeahead', templateUrl: './search-typeahead.component.html', styleUrls: [ './search-typeahead.component.scss' ] }) -export class SearchTypeaheadComponent implements OnInit, OnDestroy { - @ViewChild('searchVideo', { static: true }) searchInput: ElementRef +export class SearchTypeaheadComponent implements OnInit, AfterViewChecked, OnDestroy { + @ViewChildren(SuggestionComponent) suggestionItems: QueryList hasChannel = false inChannel = false - newSearch = true + areSuggestionsOpened = true search = '' - serverConfig: ServerConfig + serverConfig: HTMLServerConfig inThisChannelText: string keyboardEventsManager: ListKeyManager - results: Result[] = [] + results: SuggestionPayload[] = [] + + activeSearch: SuggestionPayloadType + + private scheduleKeyboardEventsInit = false constructor ( private authService: AuthService, @@ -36,125 +40,161 @@ export class SearchTypeaheadComponent implements OnInit, OnDestroy { ngOnInit () { this.route.queryParams - .pipe(first(params => params.search !== undefined && params.search !== null)) + .pipe(first(params => this.isOnSearch() && params.search !== undefined && params.search !== null)) .subscribe(params => this.search = params.search) - this.serverService.getConfig() - .subscribe(config => this.serverConfig = config) + + this.serverConfig = this.serverService.getHTMLConfig() + this.computeTypeahead() + + this.serverService.configReloaded + .subscribe(config => { + this.serverConfig = config + this.computeTypeahead() + }) } - ngOnDestroy () { - if (this.keyboardEventsManager) this.keyboardEventsManager.change.unsubscribe() + ngAfterViewChecked () { + if (this.scheduleKeyboardEventsInit && !this.keyboardEventsManager) { + // Avoid ExpressionChangedAfterItHasBeenCheckedError errors + setTimeout(() => this.initKeyboardEventsManager(), 0) + } } - get activeResult () { - return this.keyboardEventsManager?.activeItem?.result + ngOnDestroy () { + if (this.keyboardEventsManager) this.keyboardEventsManager.change.unsubscribe() } - get areInstructionsDisplayed () { + areInstructionsDisplayed () { return !this.search } - get showHelp () { - return this.search && this.newSearch && this.activeResult?.type === 'search-global' + showSearchGlobalHelp () { + return this.search && this.areSuggestionsOpened && this.keyboardEventsManager?.activeItem?.result?.type === 'search-index' } - get canSearchAnyURI () { + canSearchAnyURI () { if (!this.serverConfig) return false + return this.authService.isLoggedIn() ? this.serverConfig.search.remoteUri.users : this.serverConfig.search.remoteUri.anonymous } onSearchChange () { - this.computeResults() - } - - computeResults () { - this.newSearch = true - let results: Result[] = [] - - if (this.search) { - results = [ - /* Channel search is still unimplemented. Uncomment when it is. - { - text: this.search, - type: 'search-channel' - }, - */ - { - text: this.search, - type: 'search-instance', - default: true - }, - /* Global search is still unimplemented. Uncomment when it is. - { - text: this.search, - type: 'search-global' - }, - */ - ...results - ] + this.computeTypeahead() + } + + initKeyboardEventsManager () { + if (this.keyboardEventsManager) return + + this.keyboardEventsManager = new ListKeyManager(this.suggestionItems) + + const activeIndex = this.suggestionItems.toArray().findIndex(i => i.result.default === true) + if (activeIndex === -1) { + console.error('Cannot find active index.', { suggestionItems: this.suggestionItems }) } - this.results = results.filter( - (result: Result) => { - // if we're not in a channel or one of its videos/playlits, show all channel-related results - if (!(this.hasChannel || this.inChannel)) return !result.type.includes('channel') - // if we're in a channel, show all channel-related results except for the channel redirection itself - if (this.inChannel) return result.type !== 'channel' - // all other result types are kept - return true - } + this.updateItemsState(activeIndex) + + this.keyboardEventsManager.change.subscribe( + _ => this.updateItemsState() ) } - setEventItems (event: { items: QueryList, index?: number }) { - event.items.forEach(e => { - if (this.keyboardEventsManager.activeItem && this.keyboardEventsManager.activeItem === e) { - this.keyboardEventsManager.activeItem.active = true + computeTypeahead () { + const searchIndexConfig = this.serverConfig.search.searchIndex + + if (!this.activeSearch) { + if (searchIndexConfig.enabled && (searchIndexConfig.isDefaultSearch || searchIndexConfig.disableLocalSearch)) { + this.activeSearch = 'search-index' } else { - e.active = false + this.activeSearch = 'search-instance' } - }) + } + + this.areSuggestionsOpened = true + this.results = [] + + if (!this.search) return + + if (searchIndexConfig.enabled === false || searchIndexConfig.disableLocalSearch !== true) { + this.results.push({ + text: this.search, + type: 'search-instance', + default: this.activeSearch === 'search-instance' + }) + } + + if (searchIndexConfig.enabled) { + this.results.push({ + text: this.search, + type: 'search-index', + default: this.activeSearch === 'search-index' + }) + } + + this.scheduleKeyboardEventsInit = true } - initKeyboardEventsManager (event: { items: QueryList, index?: number }) { - if (this.keyboardEventsManager) this.keyboardEventsManager.change.unsubscribe() + updateItemsState (index?: number) { + if (index !== undefined) { + this.keyboardEventsManager.setActiveItem(index) + } - this.keyboardEventsManager = new ListKeyManager(event.items) + for (const item of this.suggestionItems) { + if (this.keyboardEventsManager.activeItem && this.keyboardEventsManager.activeItem === item) { + item.active = true + this.activeSearch = item.result.type + continue + } - if (event.index !== undefined) { - this.keyboardEventsManager.setActiveItem(event.index) - } else { - this.keyboardEventsManager.setFirstItemActive() + item.active = false } + } - this.keyboardEventsManager.change.subscribe( - _ => this.setEventItems(event) - ) + onSuggestionClicked (payload: SuggestionPayload) { + this.doSearch(this.buildSearchTarget(payload)) + } + + onSuggestionHover (index: number) { + this.updateItemsState(index) } handleKey (event: KeyboardEvent) { - event.stopImmediatePropagation() if (!this.keyboardEventsManager) return switch (event.key) { case 'ArrowDown': case 'ArrowUp': + event.stopPropagation() + this.keyboardEventsManager.onKeydown(event) break + + case 'Enter': + event.stopPropagation() + this.doSearch() + break } } - doSearch () { - this.newSearch = false + isOnSearch () { + return window.location.pathname === '/search' + } + + doSearch (searchTarget?: SearchTargetType) { + this.areSuggestionsOpened = false const queryParams: Params = {} - if (window.location.pathname === '/search' && this.route.snapshot.queryParams) { + if (this.isOnSearch() && this.route.snapshot.queryParams) { Object.assign(queryParams, this.route.snapshot.queryParams) } - Object.assign(queryParams, { search: this.search }) + if (!searchTarget) { + searchTarget = this.buildSearchTarget(this.keyboardEventsManager.activeItem.result) + } + + Object.assign(queryParams, { search: this.search, searchTarget }) const o = this.authService.isLoggedIn() ? this.loadUserLanguagesIfNeeded(queryParams) @@ -164,7 +204,7 @@ export class SearchTypeaheadComponent implements OnInit, OnDestroy { } private loadUserLanguagesIfNeeded (queryParams: any) { - if (queryParams && queryParams.languageOneOf) return of(queryParams) + if (queryParams?.languageOneOf) return of(queryParams) return this.authService.userInformationLoaded .pipe( @@ -172,4 +212,12 @@ export class SearchTypeaheadComponent implements OnInit, OnDestroy { tap(() => Object.assign(queryParams, { languageOneOf: this.authService.getUser().videoLanguages })) ) } + + private buildSearchTarget (result: SuggestionPayload): SearchTargetType { + if (result.type === 'search-index') { + return 'search-index' + } + + return 'local' + } }