aboutsummaryrefslogtreecommitdiffhomepage
path: root/client/src/app/header
diff options
context:
space:
mode:
Diffstat (limited to 'client/src/app/header')
-rw-r--r--client/src/app/header/header.component.html8
-rw-r--r--client/src/app/header/header.component.scss12
-rw-r--r--client/src/app/header/header.component.ts41
-rw-r--r--client/src/app/header/search-typeahead.component.scss16
-rw-r--r--client/src/app/header/search-typeahead.component.ts86
-rw-r--r--client/src/app/header/suggestion.component.ts7
-rw-r--r--client/src/app/header/suggestions.component.ts1
7 files changed, 86 insertions, 85 deletions
diff --git a/client/src/app/header/header.component.html b/client/src/app/header/header.component.html
index 074bebf21..561ee6c16 100644
--- a/client/src/app/header/header.component.html
+++ b/client/src/app/header/header.component.html
@@ -1,9 +1,9 @@
1<my-search-typeahead> 1<my-search-typeahead class="w-100 d-flex justify-content-end">
2 <input 2 <input
3 type="text" id="search-video" name="search-video" [attr.aria-label]="ariaLabelTextForSearch" i18n-placeholder placeholder="Search locally videos, channels…" 3 type="text" id="search-video" name="search-video" [attr.aria-label]="ariaLabelTextForSearch"
4 [(ngModel)]="searchValue" (keyup.enter)="doSearch()" 4 i18n-placeholder placeholder="Search videos, channels… known by this instance" [(ngModel)]="searchValue"
5 > 5 >
6 <span (click)="doSearch()" class="icon icon-search"></span> 6 <span class="icon icon-search"></span>
7</my-search-typeahead> 7</my-search-typeahead>
8 8
9<a class="upload-button" routerLink="/videos/upload"> 9<a class="upload-button" routerLink="/videos/upload">
diff --git a/client/src/app/header/header.component.scss b/client/src/app/header/header.component.scss
index b602cf0a8..2f0a407fc 100644
--- a/client/src/app/header/header.component.scss
+++ b/client/src/app/header/header.component.scss
@@ -14,14 +14,6 @@ my-search-typeahead {
14 &::placeholder { 14 &::placeholder {
15 color: var(--inputPlaceholderColor); 15 color: var(--inputPlaceholderColor);
16 } 16 }
17
18 @media screen and (max-width: 800px) {
19 width: calc(100% - 150px);
20 }
21
22 @media screen and (max-width: 600px) {
23 width: calc(100% - 70px);
24 }
25} 17}
26 18
27.icon.icon-search { 19.icon.icon-search {
@@ -45,10 +37,6 @@ my-search-typeahead {
45 color: var(--mainBackgroundColor) !important; 37 color: var(--mainBackgroundColor) !important;
46 margin-right: 25px; 38 margin-right: 25px;
47 39
48 @media screen and (max-width: 800px) {
49 margin-right: 0;
50 }
51
52 @media screen and (max-width: 600px) { 40 @media screen and (max-width: 600px) {
53 margin-right: 10px; 41 margin-right: 10px;
54 padding: 0 10px; 42 padding: 0 10px;
diff --git a/client/src/app/header/header.component.ts b/client/src/app/header/header.component.ts
index ca4a32cbc..d9311c554 100644
--- a/client/src/app/header/header.component.ts
+++ b/client/src/app/header/header.component.ts
@@ -1,9 +1,4 @@
1import { filter, first, map, tap } from 'rxjs/operators'
2import { Component, OnInit } from '@angular/core' 1import { Component, OnInit } from '@angular/core'
3import { ActivatedRoute, NavigationEnd, Params, Router } from '@angular/router'
4import { getParameterByName } from '../shared/misc/utils'
5import { AuthService } from '@app/core'
6import { of } from 'rxjs'
7import { I18n } from '@ngx-translate/i18n-polyfill' 2import { I18n } from '@ngx-translate/i18n-polyfill'
8 3
9@Component({ 4@Component({
@@ -17,46 +12,10 @@ export class HeaderComponent implements OnInit {
17 ariaLabelTextForSearch = '' 12 ariaLabelTextForSearch = ''
18 13
19 constructor ( 14 constructor (
20 private router: Router,
21 private route: ActivatedRoute,
22 private auth: AuthService,
23 private i18n: I18n 15 private i18n: I18n
24 ) {} 16 ) {}
25 17
26 ngOnInit () { 18 ngOnInit () {
27 this.ariaLabelTextForSearch = this.i18n('Search videos, channels') 19 this.ariaLabelTextForSearch = this.i18n('Search videos, channels')
28
29 this.router.events
30 .pipe(
31 filter(e => e instanceof NavigationEnd),
32 map(() => getParameterByName('search', window.location.href))
33 )
34 .subscribe(searchQuery => this.searchValue = searchQuery || '')
35 }
36
37 doSearch () {
38 const queryParams: Params = {}
39
40 if (window.location.pathname === '/search' && this.route.snapshot.queryParams) {
41 Object.assign(queryParams, this.route.snapshot.queryParams)
42 }
43
44 Object.assign(queryParams, { search: this.searchValue })
45
46 const o = this.auth.isLoggedIn()
47 ? this.loadUserLanguagesIfNeeded(queryParams)
48 : of(true)
49
50 o.subscribe(() => this.router.navigate([ '/search' ], { queryParams }))
51 }
52
53 private loadUserLanguagesIfNeeded (queryParams: any) {
54 if (queryParams && queryParams.languageOneOf) return of(queryParams)
55
56 return this.auth.userInformationLoaded
57 .pipe(
58 first(),
59 tap(() => Object.assign(queryParams, { languageOneOf: this.auth.getUser().videoLanguages }))
60 )
61 } 20 }
62} 21}
diff --git a/client/src/app/header/search-typeahead.component.scss b/client/src/app/header/search-typeahead.component.scss
index c410d4734..c2f5a1828 100644
--- a/client/src/app/header/search-typeahead.component.scss
+++ b/client/src/app/header/search-typeahead.component.scss
@@ -46,6 +46,18 @@ my-suggestions ::ng-deep ul {
46 transition: box-shadow .3s ease, width .2s ease; 46 transition: box-shadow .3s ease, width .2s ease;
47 } 47 }
48 48
49 @media screen and (min-width: 500px) {
50 margin-left: 10px;
51 }
52
53 @media screen and (max-width: 800px) {
54 flex: 1;
55
56 ::ng-deep input {
57 width: unset;
58 }
59 }
60
49 ::ng-deep span { 61 ::ng-deep span {
50 right: 10px; 62 right: 10px;
51 } 63 }
@@ -59,7 +71,9 @@ my-suggestions ::ng-deep ul {
59 &:focus, 71 &:focus,
60 ::ng-deep &:focus-within { 72 ::ng-deep &:focus-within {
61 & > div:last-child { 73 & > div:last-child {
62 display: initial !important; 74 @media screen and (min-width: 500px) {
75 display: initial !important;
76 }
63 77
64 #typeahead-help, 78 #typeahead-help,
65 #typeahead-instructions, 79 #typeahead-instructions,
diff --git a/client/src/app/header/search-typeahead.component.ts b/client/src/app/header/search-typeahead.component.ts
index 084bdd58b..514c04704 100644
--- a/client/src/app/header/search-typeahead.component.ts
+++ b/client/src/app/header/search-typeahead.component.ts
@@ -7,13 +7,15 @@ import {
7 OnDestroy, 7 OnDestroy,
8 QueryList 8 QueryList
9} from '@angular/core' 9} from '@angular/core'
10import { Router, NavigationEnd } from '@angular/router' 10import { Router, NavigationEnd, Params, ActivatedRoute } from '@angular/router'
11import { AuthService } from '@app/core' 11import { AuthService } from '@app/core'
12import { I18n } from '@ngx-translate/i18n-polyfill' 12import { I18n } from '@ngx-translate/i18n-polyfill'
13import { filter } from 'rxjs/operators' 13import { filter, first, tap, map } from 'rxjs/operators'
14import { ListKeyManager } from '@angular/cdk/a11y' 14import { ListKeyManager } from '@angular/cdk/a11y'
15import { UP_ARROW, DOWN_ARROW, ENTER, TAB } from '@angular/cdk/keycodes' 15import { UP_ARROW, DOWN_ARROW, ENTER, TAB } from '@angular/cdk/keycodes'
16import { SuggestionComponent } from './suggestion.component' 16import { SuggestionComponent, Result } from './suggestion.component'
17import { of } from 'rxjs'
18import { getParameterByName } from '@app/shared/misc/utils'
17 19
18@Component({ 20@Component({
19 selector: 'my-search-typeahead', 21 selector: 'my-search-typeahead',
@@ -41,6 +43,7 @@ export class SearchTypeaheadComponent implements OnInit, OnDestroy, AfterViewIni
41 constructor ( 43 constructor (
42 private authService: AuthService, 44 private authService: AuthService,
43 private router: Router, 45 private router: Router,
46 private route: ActivatedRoute,
44 private i18n: I18n 47 private i18n: I18n
45 ) { 48 ) {
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.') 49 this.URIPolicyText = this.i18n('Determines whether you can resolve any distant content, or if your instance only allows doing so for instances it follows.')
@@ -50,12 +53,19 @@ export class SearchTypeaheadComponent implements OnInit, OnDestroy, AfterViewIni
50 53
51 ngOnInit () { 54 ngOnInit () {
52 this.router.events 55 this.router.events
53 .pipe(filter(event => event instanceof NavigationEnd)) 56 .pipe(filter(e => e instanceof NavigationEnd))
54 .subscribe((event: NavigationEnd) => { 57 .subscribe((event: NavigationEnd) => {
55 this.hasChannel = event.url.startsWith('/videos/watch') 58 this.hasChannel = event.url.startsWith('/videos/watch')
56 this.inChannel = event.url.startsWith('/video-channels') 59 this.inChannel = event.url.startsWith('/video-channels')
57 this.computeResults() 60 this.computeResults()
58 }) 61 })
62
63 this.router.events
64 .pipe(
65 filter(e => e instanceof NavigationEnd),
66 map(() => getParameterByName('search', window.location.href))
67 )
68 .subscribe(searchQuery => this.searchInput.value = searchQuery || '')
59 } 69 }
60 70
61 ngOnDestroy () { 71 ngOnDestroy () {
@@ -82,33 +92,33 @@ export class SearchTypeaheadComponent implements OnInit, OnDestroy, AfterViewIni
82 92
83 computeResults () { 93 computeResults () {
84 this.newSearch = true 94 this.newSearch = true
85 let results = [ 95 let results: Result[] = []
86 {
87 text: 'Maître poney',
88 type: 'channel'
89 }
90 ]
91 96
92 if (this.hasSearch) { 97 if (this.hasSearch) {
93 results = [ 98 results = [
99 /* Channel search is still unimplemented. Uncomment when it is.
94 { 100 {
95 text: this.searchInput.value, 101 text: this.searchInput.value,
96 type: 'search-channel' 102 type: 'search-channel'
97 }, 103 },
104 */
98 { 105 {
99 text: this.searchInput.value, 106 text: this.searchInput.value,
100 type: 'search-instance' 107 type: 'search-instance',
108 default: true
101 }, 109 },
110 /* Global search is still unimplemented. Uncomment when it is.
102 { 111 {
103 text: this.searchInput.value, 112 text: this.searchInput.value,
104 type: 'search-global' 113 type: 'search-global'
105 }, 114 },
115 */
106 ...results 116 ...results
107 ] 117 ]
108 } 118 }
109 119
110 this.results = results.filter( 120 this.results = results.filter(
111 result => { 121 (result: Result) => {
112 // if we're not in a channel or one of its videos/playlits, show all channel-related results 122 // if we're not in a channel or one of its videos/playlits, show all channel-related results
113 if (!(this.hasChannel || this.inChannel)) return !result.type.includes('channel') 123 if (!(this.hasChannel || this.inChannel)) return !result.type.includes('channel')
114 // if we're in a channel, show all channel-related results except for the channel redirection itself 124 // if we're in a channel, show all channel-related results except for the channel redirection itself
@@ -118,19 +128,26 @@ export class SearchTypeaheadComponent implements OnInit, OnDestroy, AfterViewIni
118 ) 128 )
119 } 129 }
120 130
131 setEventItems (event: { items: QueryList<SuggestionComponent>, index?: number }) {
132 event.items.forEach(e => {
133 if (this.keyboardEventsManager.activeItem && this.keyboardEventsManager.activeItem === e) {
134 this.keyboardEventsManager.activeItem.active = true
135 } else {
136 e.active = false
137 }
138 })
139 }
140
121 initKeyboardEventsManager (event: { items: QueryList<SuggestionComponent>, index?: number }) { 141 initKeyboardEventsManager (event: { items: QueryList<SuggestionComponent>, index?: number }) {
122 if (this.keyboardEventsManager) this.keyboardEventsManager.change.unsubscribe() 142 if (this.keyboardEventsManager) this.keyboardEventsManager.change.unsubscribe()
123 this.keyboardEventsManager = new ListKeyManager(event.items) 143 this.keyboardEventsManager = new ListKeyManager(event.items)
124 if (event.index !== undefined) { 144 if (event.index !== undefined) {
125 this.keyboardEventsManager.setActiveItem(event.index) 145 this.keyboardEventsManager.setActiveItem(event.index)
126 event.items.forEach(e => e.active = false) 146 } else {
127 this.keyboardEventsManager.activeItem.active = true 147 this.keyboardEventsManager.setFirstItemActive()
128 } 148 }
129 this.keyboardEventsManager.change.subscribe( 149 this.keyboardEventsManager.change.subscribe(
130 val => { 150 _ => this.setEventItems(event)
131 event.items.forEach(e => e.active = false)
132 this.keyboardEventsManager.activeItem.active = true
133 }
134 ) 151 )
135 } 152 }
136 153
@@ -141,17 +158,40 @@ export class SearchTypeaheadComponent implements OnInit, OnDestroy, AfterViewIni
141 handleKeyUp (event: KeyboardEvent, indexSelected?: number) { 158 handleKeyUp (event: KeyboardEvent, indexSelected?: number) {
142 event.stopImmediatePropagation() 159 event.stopImmediatePropagation()
143 if (this.keyboardEventsManager) { 160 if (this.keyboardEventsManager) {
144 if (event.keyCode === TAB) { 161 if (event.keyCode === DOWN_ARROW || event.keyCode === UP_ARROW) {
145 this.keyboardEventsManager.setNextItemActive()
146 return false
147 } else if (event.keyCode === DOWN_ARROW || event.keyCode === UP_ARROW) {
148 this.keyboardEventsManager.onKeydown(event) 162 this.keyboardEventsManager.onKeydown(event)
149 return false 163 return false
150 } else if (event.keyCode === ENTER) { 164 } else if (event.keyCode === ENTER) {
151 this.newSearch = false 165 this.newSearch = false
152 // this.router.navigate(this.keyboardEventsManager.activeItem.result) 166 this.doSearch()
153 return false 167 return false
154 } 168 }
155 } 169 }
156 } 170 }
171
172 doSearch () {
173 const queryParams: Params = {}
174
175 if (window.location.pathname === '/search' && this.route.snapshot.queryParams) {
176 Object.assign(queryParams, this.route.snapshot.queryParams)
177 }
178
179 Object.assign(queryParams, { search: this.searchInput.value })
180
181 const o = this.authService.isLoggedIn()
182 ? this.loadUserLanguagesIfNeeded(queryParams)
183 : of(true)
184
185 o.subscribe(() => this.router.navigate([ '/search' ], { queryParams }))
186 }
187
188 private loadUserLanguagesIfNeeded (queryParams: any) {
189 if (queryParams && queryParams.languageOneOf) return of(queryParams)
190
191 return this.authService.userInformationLoaded
192 .pipe(
193 first(),
194 tap(() => Object.assign(queryParams, { languageOneOf: this.authService.getUser().videoLanguages }))
195 )
196 }
157} 197}
diff --git a/client/src/app/header/suggestion.component.ts b/client/src/app/header/suggestion.component.ts
index 75c44a583..bdcb3e03f 100644
--- a/client/src/app/header/suggestion.component.ts
+++ b/client/src/app/header/suggestion.component.ts
@@ -3,10 +3,11 @@ import { RouterLink } from '@angular/router'
3import { I18n } from '@ngx-translate/i18n-polyfill' 3import { I18n } from '@ngx-translate/i18n-polyfill'
4import { ListKeyManagerOption } from '@angular/cdk/a11y' 4import { ListKeyManagerOption } from '@angular/cdk/a11y'
5 5
6type Result = { 6export type Result = {
7 text: string 7 text: string
8 type: 'channel' | 'suggestion' | 'search-channel' | 'search-instance' | 'search-global' | 'search-any' 8 type: 'channel' | 'suggestion' | 'search-channel' | 'search-instance' | 'search-global' | 'search-any'
9 routerLink?: RouterLink 9 routerLink?: RouterLink,
10 default?: boolean
10} 11}
11 12
12@Component({ 13@Component({
@@ -39,7 +40,7 @@ export class SuggestionComponent implements OnInit, ListKeyManagerOption {
39 } 40 }
40 41
41 ngOnInit () { 42 ngOnInit () {
42 this.active = false 43 if (this.result.default) this.active = true
43 } 44 }
44 45
45 selectItem () { 46 selectItem () {
diff --git a/client/src/app/header/suggestions.component.ts b/client/src/app/header/suggestions.component.ts
index 122c09388..fac7fe2f9 100644
--- a/client/src/app/header/suggestions.component.ts
+++ b/client/src/app/header/suggestions.component.ts
@@ -19,7 +19,6 @@ export class SuggestionsComponent implements AfterViewInit {
19 @Output() init = new EventEmitter() 19 @Output() init = new EventEmitter()
20 20
21 ngAfterViewInit () { 21 ngAfterViewInit () {
22 this.init.emit({ items: this.listItems })
23 this.listItems.changes.subscribe( 22 this.listItems.changes.subscribe(
24 val => this.init.emit({ items: this.listItems }) 23 val => this.init.emit({ items: this.listItems })
25 ) 24 )