aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorChocobozzz <me@florianbigard.com>2021-11-03 14:23:55 +0100
committerChocobozzz <me@florianbigard.com>2021-11-03 14:23:55 +0100
commitdd6d2a7ce50e7ff02e00995ccc87f8f929ad9709 (patch)
treeb71ed391c2d8f99bff40dd3461010876de7bb23c
parentd324756edb836672f12284cd18e642a658b273d8 (diff)
downloadPeerTube-dd6d2a7ce50e7ff02e00995ccc87f8f929ad9709.tar.gz
PeerTube-dd6d2a7ce50e7ff02e00995ccc87f8f929ad9709.tar.zst
PeerTube-dd6d2a7ce50e7ff02e00995ccc87f8f929ad9709.zip
Improve advanced input filter
-rw-r--r--client/src/app/+admin/moderation/video-block-list/video-block-list.component.ts4
-rw-r--r--client/src/app/+admin/moderation/video-comment-list/video-comment-list.component.ts4
-rw-r--r--client/src/app/+admin/overview/users/user-list/user-list.component.ts2
-rw-r--r--client/src/app/+admin/overview/videos/video-admin.service.ts18
-rw-r--r--client/src/app/+admin/overview/videos/video-list.component.ts2
-rw-r--r--client/src/app/+my-library/my-follows/my-followers.component.ts2
-rw-r--r--client/src/app/+my-library/my-videos/my-videos.component.ts4
-rw-r--r--client/src/app/core/rest/rest.service.ts14
-rw-r--r--client/src/app/shared/shared-abuse-list/abuse-list-table.component.ts10
-rw-r--r--client/src/app/shared/shared-forms/advanced-input-filter.component.html6
-rw-r--r--client/src/app/shared/shared-forms/advanced-input-filter.component.scss23
-rw-r--r--client/src/app/shared/shared-forms/advanced-input-filter.component.ts61
-rw-r--r--shared/core-utils/utils/array.ts13
-rw-r--r--shared/core-utils/utils/index.ts1
14 files changed, 131 insertions, 33 deletions
diff --git a/client/src/app/+admin/moderation/video-block-list/video-block-list.component.ts b/client/src/app/+admin/moderation/video-block-list/video-block-list.component.ts
index 1fe8d0f9d..dca746f4e 100644
--- a/client/src/app/+admin/moderation/video-block-list/video-block-list.component.ts
+++ b/client/src/app/+admin/moderation/video-block-list/video-block-list.component.ts
@@ -30,11 +30,11 @@ export class VideoBlockListComponent extends RestTable implements OnInit {
30 title: $localize`Advanced filters`, 30 title: $localize`Advanced filters`,
31 children: [ 31 children: [
32 { 32 {
33 queryParams: { search: 'type:auto' }, 33 value: 'type:auto',
34 label: $localize`Automatic blocks` 34 label: $localize`Automatic blocks`
35 }, 35 },
36 { 36 {
37 queryParams: { search: 'type:manual' }, 37 value: 'type:manual',
38 label: $localize`Manual blocks` 38 label: $localize`Manual blocks`
39 } 39 }
40 ] 40 ]
diff --git a/client/src/app/+admin/moderation/video-comment-list/video-comment-list.component.ts b/client/src/app/+admin/moderation/video-comment-list/video-comment-list.component.ts
index a60b228af..25fe65133 100644
--- a/client/src/app/+admin/moderation/video-comment-list/video-comment-list.component.ts
+++ b/client/src/app/+admin/moderation/video-comment-list/video-comment-list.component.ts
@@ -47,11 +47,11 @@ export class VideoCommentListComponent extends RestTable implements OnInit {
47 title: $localize`Advanced filters`, 47 title: $localize`Advanced filters`,
48 children: [ 48 children: [
49 { 49 {
50 queryParams: { search: 'local:true' }, 50 value: 'local:true',
51 label: $localize`Local comments` 51 label: $localize`Local comments`
52 }, 52 },
53 { 53 {
54 queryParams: { search: 'local:false' }, 54 value: 'local:false',
55 label: $localize`Remote comments` 55 label: $localize`Remote comments`
56 } 56 }
57 ] 57 ]
diff --git a/client/src/app/+admin/overview/users/user-list/user-list.component.ts b/client/src/app/+admin/overview/users/user-list/user-list.component.ts
index 548e6e80f..9fba11cbd 100644
--- a/client/src/app/+admin/overview/users/user-list/user-list.component.ts
+++ b/client/src/app/+admin/overview/users/user-list/user-list.component.ts
@@ -39,7 +39,7 @@ export class UserListComponent extends RestTable implements OnInit {
39 title: $localize`Advanced filters`, 39 title: $localize`Advanced filters`,
40 children: [ 40 children: [
41 { 41 {
42 queryParams: { search: 'banned:true' }, 42 value: 'banned:true',
43 label: $localize`Banned users` 43 label: $localize`Banned users`
44 } 44 }
45 ] 45 ]
diff --git a/client/src/app/+admin/overview/videos/video-admin.service.ts b/client/src/app/+admin/overview/videos/video-admin.service.ts
index b90fe22d8..f80de7acd 100644
--- a/client/src/app/+admin/overview/videos/video-admin.service.ts
+++ b/client/src/app/+admin/overview/videos/video-admin.service.ts
@@ -44,11 +44,11 @@ export class VideoAdminService {
44 title: $localize`Video type`, 44 title: $localize`Video type`,
45 children: [ 45 children: [
46 { 46 {
47 queryParams: { search: 'isLive:false' }, 47 value: 'isLive:false',
48 label: $localize`VOD` 48 label: $localize`VOD`
49 }, 49 },
50 { 50 {
51 queryParams: { search: 'isLive:true' }, 51 value: 'isLive:true',
52 label: $localize`Live` 52 label: $localize`Live`
53 } 53 }
54 ] 54 ]
@@ -58,19 +58,19 @@ export class VideoAdminService {
58 title: $localize`Video files`, 58 title: $localize`Video files`,
59 children: [ 59 children: [
60 { 60 {
61 queryParams: { search: 'webtorrent:true' }, 61 value: 'webtorrent:true',
62 label: $localize`With WebTorrent` 62 label: $localize`With WebTorrent`
63 }, 63 },
64 { 64 {
65 queryParams: { search: 'webtorrent:false' }, 65 value: 'webtorrent:false',
66 label: $localize`Without WebTorrent` 66 label: $localize`Without WebTorrent`
67 }, 67 },
68 { 68 {
69 queryParams: { search: 'hls:true' }, 69 value: 'hls:true',
70 label: $localize`With HLS` 70 label: $localize`With HLS`
71 }, 71 },
72 { 72 {
73 queryParams: { search: 'hls:false' }, 73 value: 'hls:false',
74 label: $localize`Without HLS` 74 label: $localize`Without HLS`
75 } 75 }
76 ] 76 ]
@@ -80,11 +80,11 @@ export class VideoAdminService {
80 title: $localize`Videos scope`, 80 title: $localize`Videos scope`,
81 children: [ 81 children: [
82 { 82 {
83 queryParams: { search: 'isLocal:false' }, 83 value: 'isLocal:false',
84 label: $localize`Remote videos` 84 label: $localize`Remote videos`
85 }, 85 },
86 { 86 {
87 queryParams: { search: 'isLocal:true' }, 87 value: 'isLocal:true',
88 label: $localize`Local videos` 88 label: $localize`Local videos`
89 } 89 }
90 ] 90 ]
@@ -94,7 +94,7 @@ export class VideoAdminService {
94 title: $localize`Exclude`, 94 title: $localize`Exclude`,
95 children: [ 95 children: [
96 { 96 {
97 queryParams: { search: 'excludeMuted' }, 97 value: 'excludeMuted',
98 label: $localize`Exclude muted accounts` 98 label: $localize`Exclude muted accounts`
99 } 99 }
100 ] 100 ]
diff --git a/client/src/app/+admin/overview/videos/video-list.component.ts b/client/src/app/+admin/overview/videos/video-list.component.ts
index 635552cf5..0f98a5d33 100644
--- a/client/src/app/+admin/overview/videos/video-list.component.ts
+++ b/client/src/app/+admin/overview/videos/video-list.component.ts
@@ -86,7 +86,7 @@ export class VideoListComponent extends RestTable implements OnInit {
86 } 86 }
87 87
88 getPrivacyBadgeClass (video: Video) { 88 getPrivacyBadgeClass (video: Video) {
89 if (video.privacy.id === VideoPrivacy.PUBLIC) return 'badge-blue' 89 if (video.privacy.id === VideoPrivacy.PUBLIC) return 'badge-green'
90 90
91 return 'badge-yellow' 91 return 'badge-yellow'
92 } 92 }
diff --git a/client/src/app/+my-library/my-follows/my-followers.component.ts b/client/src/app/+my-library/my-follows/my-followers.component.ts
index 4a72b983f..0dd9bf6f5 100644
--- a/client/src/app/+my-library/my-follows/my-followers.component.ts
+++ b/client/src/app/+my-library/my-follows/my-followers.component.ts
@@ -39,7 +39,7 @@ export class MyFollowersComponent implements OnInit {
39 this.auth.userInformationLoaded.subscribe(() => { 39 this.auth.userInformationLoaded.subscribe(() => {
40 const channelFilters = this.auth.getUser().videoChannels.map(c => { 40 const channelFilters = this.auth.getUser().videoChannels.map(c => {
41 return { 41 return {
42 queryParams: { search: 'channel:' + c.name }, 42 value: 'channel:' + c.name,
43 label: c.name 43 label: c.name
44 } 44 }
45 }) 45 })
diff --git a/client/src/app/+my-library/my-videos/my-videos.component.ts b/client/src/app/+my-library/my-videos/my-videos.component.ts
index a117d0915..261e87f99 100644
--- a/client/src/app/+my-library/my-videos/my-videos.component.ts
+++ b/client/src/app/+my-library/my-videos/my-videos.component.ts
@@ -82,7 +82,7 @@ export class MyVideosComponent implements OnInit, DisableForReuseHook {
82 82
83 const channelFilters = this.userChannels.map(c => { 83 const channelFilters = this.userChannels.map(c => {
84 return { 84 return {
85 queryParams: { search: 'channel:' + c.name }, 85 value: 'channel:' + c.name,
86 label: c.name 86 label: c.name
87 } 87 }
88 }) 88 })
@@ -92,7 +92,7 @@ export class MyVideosComponent implements OnInit, DisableForReuseHook {
92 title: $localize`Advanced filters`, 92 title: $localize`Advanced filters`,
93 children: [ 93 children: [
94 { 94 {
95 queryParams: { search: 'isLive:true' }, 95 value: 'isLive:true',
96 label: $localize`Only live videos` 96 label: $localize`Only live videos`
97 } 97 }
98 ] 98 ]
diff --git a/client/src/app/core/rest/rest.service.ts b/client/src/app/core/rest/rest.service.ts
index 59152e658..b2a5a3f72 100644
--- a/client/src/app/core/rest/rest.service.ts
+++ b/client/src/app/core/rest/rest.service.ts
@@ -82,13 +82,11 @@ export class RestService {
82 parseQueryStringFilter <T extends QueryStringFilterPrefixes> (q: string, prefixes: T): ParseQueryStringFiltersResult<keyof T> { 82 parseQueryStringFilter <T extends QueryStringFilterPrefixes> (q: string, prefixes: T): ParseQueryStringFiltersResult<keyof T> {
83 if (!q) return {} 83 if (!q) return {}
84 84
85 // Tokenize the strings using spaces that are not in quotes 85 const tokens = this.tokenizeString(q)
86 const tokens = q.match(/(?:[^\s"]+|"[^"]*")+/g)
87 .filter(token => !!token)
88 86
89 // Build prefix array 87 // Build prefix array
90 const prefixeStrings = Object.values(prefixes) 88 const prefixeStrings = Object.values(prefixes)
91 .map(p => p.prefix) 89 .map(p => p.prefix)
92 90
93 logger(`Built tokens "${tokens.join(', ')}" for prefixes "${prefixeStrings.join(', ')}"`) 91 logger(`Built tokens "${tokens.join(', ')}" for prefixes "${prefixeStrings.join(', ')}"`)
94 92
@@ -137,4 +135,12 @@ export class RestService {
137 ...additionalFilters 135 ...additionalFilters
138 } 136 }
139 } 137 }
138
139 tokenizeString (q: string) {
140 if (!q) return []
141
142 // Tokenize the strings using spaces that are not in quotes
143 return q.match(/(?:[^\s"]+|"[^"]*")+/g)
144 .filter(token => !!token)
145 }
140} 146}
diff --git a/client/src/app/shared/shared-abuse-list/abuse-list-table.component.ts b/client/src/app/shared/shared-abuse-list/abuse-list-table.component.ts
index 10f5861b9..b902726fa 100644
--- a/client/src/app/shared/shared-abuse-list/abuse-list-table.component.ts
+++ b/client/src/app/shared/shared-abuse-list/abuse-list-table.component.ts
@@ -39,23 +39,23 @@ export class AbuseListTableComponent extends RestTable implements OnInit {
39 title: $localize`Advanced filters`, 39 title: $localize`Advanced filters`,
40 children: [ 40 children: [
41 { 41 {
42 queryParams: { search: 'state:pending' }, 42 value: 'state:pending',
43 label: $localize`Unsolved reports` 43 label: $localize`Unsolved reports`
44 }, 44 },
45 { 45 {
46 queryParams: { search: 'state:accepted' }, 46 value: 'state:accepted',
47 label: $localize`Accepted reports` 47 label: $localize`Accepted reports`
48 }, 48 },
49 { 49 {
50 queryParams: { search: 'state:rejected' }, 50 value: 'state:rejected',
51 label: $localize`Refused reports` 51 label: $localize`Refused reports`
52 }, 52 },
53 { 53 {
54 queryParams: { search: 'videoIs:blacklisted' }, 54 value: 'videoIs:blacklisted',
55 label: $localize`Reports with blocked videos` 55 label: $localize`Reports with blocked videos`
56 }, 56 },
57 { 57 {
58 queryParams: { search: 'videoIs:deleted' }, 58 value: 'videoIs:deleted',
59 label: $localize`Reports with deleted videos` 59 label: $localize`Reports with deleted videos`
60 } 60 }
61 ] 61 ]
diff --git a/client/src/app/shared/shared-forms/advanced-input-filter.component.html b/client/src/app/shared/shared-forms/advanced-input-filter.component.html
index c662b9bb6..7031cb53b 100644
--- a/client/src/app/shared/shared-forms/advanced-input-filter.component.html
+++ b/client/src/app/shared/shared-forms/advanced-input-filter.component.html
@@ -8,9 +8,11 @@
8 <ng-container *ngFor="let group of filters"> 8 <ng-container *ngFor="let group of filters">
9 <h6 class="dropdown-header">{{ group.title }}</h6> 9 <h6 class="dropdown-header">{{ group.title }}</h6>
10 10
11 <a *ngFor="let filter of group.children" [routerLink]="[ '.' ]" [queryParams]="filter.queryParams" class="dropdown-item"> 11 <button *ngFor="let filter of group.children" (click)="onFilterClick(filter)" class="dropdown-item">
12 <my-global-icon [ngClass]="{ 'no-visible': !isFilterEnabled(filter) }" iconName="tick"></my-global-icon>
13
12 {{ filter.label }} 14 {{ filter.label }}
13 </a> 15 </button>
14 </ng-container> 16 </ng-container>
15 </div> 17 </div>
16 </div> 18 </div>
diff --git a/client/src/app/shared/shared-forms/advanced-input-filter.component.scss b/client/src/app/shared/shared-forms/advanced-input-filter.component.scss
index 07a43761c..ee1b3b508 100644
--- a/client/src/app/shared/shared-forms/advanced-input-filter.component.scss
+++ b/client/src/app/shared/shared-forms/advanced-input-filter.component.scss
@@ -1,6 +1,10 @@
1@use '_variables' as *; 1@use '_variables' as *;
2@use '_mixins' as *; 2@use '_mixins' as *;
3 3
4.dropdown-item {
5 font-size: 14px;
6}
7
4input { 8input {
5 @include peertube-input-text(250px); 9 @include peertube-input-text(250px);
6} 10}
@@ -8,3 +12,22 @@ input {
8.input-group-text { 12.input-group-text {
9 background-color: transparent; 13 background-color: transparent;
10} 14}
15
16my-global-icon {
17 $size: 18px;
18 $margin: 2px;
19
20 @include margin-right($margin);
21
22 opacity: 1;
23 width: 18px;
24 height: 18px;
25
26
27 &.no-visible {
28 @include margin-right($size + $margin);
29
30 width: 0;
31 height: 0;
32 }
33}
diff --git a/client/src/app/shared/shared-forms/advanced-input-filter.component.ts b/client/src/app/shared/shared-forms/advanced-input-filter.component.ts
index a12dddf7a..d8aeaa0fa 100644
--- a/client/src/app/shared/shared-forms/advanced-input-filter.component.ts
+++ b/client/src/app/shared/shared-forms/advanced-input-filter.component.ts
@@ -3,14 +3,17 @@ import { Subject } from 'rxjs'
3import { debounceTime, distinctUntilChanged } from 'rxjs/operators' 3import { debounceTime, distinctUntilChanged } from 'rxjs/operators'
4import { AfterViewInit, Component, EventEmitter, Input, OnInit, Output } from '@angular/core' 4import { AfterViewInit, Component, EventEmitter, Input, OnInit, Output } from '@angular/core'
5import { ActivatedRoute, Params, Router } from '@angular/router' 5import { ActivatedRoute, Params, Router } from '@angular/router'
6import { RestService } from '@app/core'
6 7
7export type AdvancedInputFilter = { 8export type AdvancedInputFilter = {
8 title: string 9 title: string
9 10
10 children: { 11 children: AdvancedInputFilterChild[]
11 label: string 12}
12 queryParams: Params 13
13 }[] 14export type AdvancedInputFilterChild = {
15 label: string
16 value: string
14} 17}
15 18
16const logger = debug('peertube:AdvancedInputFilterComponent') 19const logger = debug('peertube:AdvancedInputFilterComponent')
@@ -28,6 +31,8 @@ export class AdvancedInputFilterComponent implements OnInit, AfterViewInit {
28 31
29 searchValue: string 32 searchValue: string
30 33
34 private enabledFilters = new Set<string>()
35
31 private searchStream: Subject<string> 36 private searchStream: Subject<string>
32 37
33 private viewInitialized = false 38 private viewInitialized = false
@@ -35,6 +40,7 @@ export class AdvancedInputFilterComponent implements OnInit, AfterViewInit {
35 40
36 constructor ( 41 constructor (
37 private route: ActivatedRoute, 42 private route: ActivatedRoute,
43 private restService: RestService,
38 private router: Router 44 private router: Router
39 ) { } 45 ) { }
40 46
@@ -62,6 +68,18 @@ export class AdvancedInputFilterComponent implements OnInit, AfterViewInit {
62 return this.filters && this.filters.length !== 0 68 return this.filters && this.filters.length !== 0
63 } 69 }
64 70
71 isFilterEnabled (filter: AdvancedInputFilterChild) {
72 return this.enabledFilters.has(filter.value)
73 }
74
75 onFilterClick (filter: AdvancedInputFilterChild) {
76 const newSearch = this.isFilterEnabled(filter)
77 ? this.removeFilterToSearch(this.searchValue, filter)
78 : this.addFilterToSearch(this.searchValue, filter)
79
80 this.router.navigate([ '.' ], { relativeTo: this.route, queryParams: { search: newSearch.trim() } })
81 }
82
65 private scheduleSearchUpdate (value: string) { 83 private scheduleSearchUpdate (value: string) {
66 this.searchValue = value 84 this.searchValue = value
67 this.searchStream.next(this.searchValue) 85 this.searchStream.next(this.searchValue)
@@ -71,6 +89,7 @@ export class AdvancedInputFilterComponent implements OnInit, AfterViewInit {
71 this.searchValue = value 89 this.searchValue = value
72 90
73 this.setQueryParams(this.searchValue) 91 this.setQueryParams(this.searchValue)
92 this.parseFilters(this.searchValue)
74 this.emitSearch() 93 this.emitSearch()
75 } 94 }
76 95
@@ -84,6 +103,9 @@ export class AdvancedInputFilterComponent implements OnInit, AfterViewInit {
84 if (this.searchValue === search) return 103 if (this.searchValue === search) return
85 104
86 this.searchValue = search 105 this.searchValue = search
106
107 this.parseFilters(this.searchValue)
108
87 this.emitSearch() 109 this.emitSearch()
88 }) 110 })
89 } 111 }
@@ -98,6 +120,7 @@ export class AdvancedInputFilterComponent implements OnInit, AfterViewInit {
98 ) 120 )
99 .subscribe(() => { 121 .subscribe(() => {
100 this.setQueryParams(this.searchValue) 122 this.setQueryParams(this.searchValue)
123 this.parseFilters(this.searchValue)
101 124
102 this.emitSearch() 125 this.emitSearch()
103 }) 126 })
@@ -120,4 +143,34 @@ export class AdvancedInputFilterComponent implements OnInit, AfterViewInit {
120 if (search) Object.assign(queryParams, { search }) 143 if (search) Object.assign(queryParams, { search })
121 this.router.navigate([ ], { queryParams }) 144 this.router.navigate([ ], { queryParams })
122 } 145 }
146
147 private removeFilterToSearch (search: string, removedFilter: AdvancedInputFilterChild) {
148 return search.replace(removedFilter.value, '')
149 }
150
151 private addFilterToSearch (search: string, newFilter: AdvancedInputFilterChild) {
152 const prefix = newFilter.value.split(':').shift()
153
154 // Tokenize search and remove a potential existing filter
155 const tokens = this.restService.tokenizeString(search)
156 .filter(t => !t.startsWith(prefix))
157
158 tokens.push(newFilter.value)
159
160 return tokens.join(' ')
161 }
162
163 private parseFilters (search: string) {
164 const tokens = this.restService.tokenizeString(search)
165
166 this.enabledFilters = new Set()
167
168 for (const group of this.filters) {
169 for (const filter of group.children) {
170 if (tokens.includes(filter.value)) {
171 this.enabledFilters.add(filter.value)
172 }
173 }
174 }
175 }
123} 176}
diff --git a/shared/core-utils/utils/array.ts b/shared/core-utils/utils/array.ts
new file mode 100644
index 000000000..9e326a5aa
--- /dev/null
+++ b/shared/core-utils/utils/array.ts
@@ -0,0 +1,13 @@
1function findCommonElement <T> (array1: T[], array2: T[]) {
2 for (const a of array1) {
3 for (const b of array2) {
4 if (a === b) return a
5 }
6 }
7
8 return null
9}
10
11export {
12 findCommonElement
13}
diff --git a/shared/core-utils/utils/index.ts b/shared/core-utils/utils/index.ts
index a71977d88..8d16365a8 100644
--- a/shared/core-utils/utils/index.ts
+++ b/shared/core-utils/utils/index.ts
@@ -1 +1,2 @@
1export * from './array'
1export * from './object' 2export * from './object'