aboutsummaryrefslogtreecommitdiffhomepage
path: root/client/src/app
diff options
context:
space:
mode:
Diffstat (limited to 'client/src/app')
-rw-r--r--client/src/app/+admin/moderation/abuse-list/abuse-list.component.html2
-rw-r--r--client/src/app/+admin/moderation/video-block-list/video-block-list.component.html20
-rw-r--r--client/src/app/+admin/moderation/video-block-list/video-block-list.component.ts31
-rw-r--r--client/src/app/+admin/moderation/video-comment-list/video-comment-list.component.html20
-rw-r--r--client/src/app/+admin/moderation/video-comment-list/video-comment-list.component.ts12
-rw-r--r--client/src/app/+admin/users/user-list/user-list.component.html19
-rw-r--r--client/src/app/+admin/users/user-list/user-list.component.scss22
-rw-r--r--client/src/app/+admin/users/user-list/user-list.component.ts22
-rw-r--r--client/src/app/+my-account/my-account-abuses/my-account-abuses-list.component.html2
-rw-r--r--client/src/app/+my-library/my-videos/my-videos.component.html7
-rw-r--r--client/src/app/+my-library/my-videos/my-videos.component.ts40
-rw-r--r--client/src/app/core/rest/rest-table.ts72
-rw-r--r--client/src/app/core/routing/index.ts1
-rw-r--r--client/src/app/core/routing/route-filter.ts79
-rw-r--r--client/src/app/shared/shared-abuse-list/abuse-details.component.html12
-rw-r--r--client/src/app/shared/shared-abuse-list/abuse-details.component.ts2
-rw-r--r--client/src/app/shared/shared-abuse-list/abuse-list-table.component.html25
-rw-r--r--client/src/app/shared/shared-abuse-list/abuse-list-table.component.ts25
-rw-r--r--client/src/app/shared/shared-actor-image/actor-avatar.component.ts2
-rw-r--r--client/src/app/shared/shared-forms/advanced-input-filter.component.html22
-rw-r--r--client/src/app/shared/shared-forms/advanced-input-filter.component.scss10
-rw-r--r--client/src/app/shared/shared-forms/advanced-input-filter.component.ts27
-rw-r--r--client/src/app/shared/shared-forms/index.ts10
-rw-r--r--client/src/app/shared/shared-forms/shared-form.module.ts9
-rw-r--r--client/src/app/shared/shared-main/video/video.service.ts18
-rw-r--r--client/src/app/shared/shared-search/advanced-search.model.ts6
26 files changed, 280 insertions, 237 deletions
diff --git a/client/src/app/+admin/moderation/abuse-list/abuse-list.component.html b/client/src/app/+admin/moderation/abuse-list/abuse-list.component.html
index 9a6c124e1..a9e0931f8 100644
--- a/client/src/app/+admin/moderation/abuse-list/abuse-list.component.html
+++ b/client/src/app/+admin/moderation/abuse-list/abuse-list.component.html
@@ -3,4 +3,4 @@
3 <ng-container i18n>Reports</ng-container> 3 <ng-container i18n>Reports</ng-container>
4</h1> 4</h1>
5 5
6<my-abuse-list-table viewType="admin" baseRoute="/admin/moderation/abuses/list"></my-abuse-list-table> 6<my-abuse-list-table viewType="admin"></my-abuse-list-table>
diff --git a/client/src/app/+admin/moderation/video-block-list/video-block-list.component.html b/client/src/app/+admin/moderation/video-block-list/video-block-list.component.html
index c7275de1b..cf2466bdb 100644
--- a/client/src/app/+admin/moderation/video-block-list/video-block-list.component.html
+++ b/client/src/app/+admin/moderation/video-block-list/video-block-list.component.html
@@ -13,25 +13,7 @@
13 <ng-template pTemplate="caption"> 13 <ng-template pTemplate="caption">
14 <div class="caption"> 14 <div class="caption">
15 <div class="ml-auto"> 15 <div class="ml-auto">
16 <div class="input-group has-feedback has-clear"> 16 <my-advanced-input-filter [filters]="inputFilters" (search)="onSearch($event)" (resetTableFilter)="resetTableFilter()"></my-advanced-input-filter>
17 <div class="input-group-prepend c-hand" ngbDropdown placement="bottom-left auto" container="body">
18 <div class="input-group-text" ngbDropdownToggle>
19 <span class="caret" aria-haspopup="menu" role="button"></span>
20 </div>
21
22 <div role="menu" ngbDropdownMenu>
23 <h6 class="dropdown-header" i18n>Advanced block filters</h6>
24 <a [routerLink]="[ '/admin/moderation/video-blocks/list' ]" [queryParams]="{ 'search': 'type:auto' }" class="dropdown-item" i18n>Automatic blocks</a>
25 <a [routerLink]="[ '/admin/moderation/video-blocks/list' ]" [queryParams]="{ 'search': 'type:manual' }" class="dropdown-item" i18n>Manual blocks</a>
26 </div>
27 </div>
28 <input
29 type="text" name="table-filter" id="table-filter" i18n-placeholder placeholder="Filter..."
30 (keyup)="onSearch($event)"
31 >
32 <a class="glyphicon glyphicon-remove-sign form-control-feedback form-control-clear" (click)="resetTableFilter()"></a>
33 <span class="sr-only" i18n>Clear filters</span>
34 </div>
35 </div> 17 </div>
36 </div> 18 </div>
37 </ng-template> 19 </ng-template>
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 d6aca10e7..dfd8dc745 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
@@ -6,6 +6,7 @@ import { AfterViewInit, Component, OnInit } from '@angular/core'
6import { DomSanitizer } from '@angular/platform-browser' 6import { DomSanitizer } from '@angular/platform-browser'
7import { ActivatedRoute, Params, Router } from '@angular/router' 7import { ActivatedRoute, Params, Router } from '@angular/router'
8import { ConfirmService, MarkdownService, Notifier, RestPagination, RestTable, ServerService } from '@app/core' 8import { ConfirmService, MarkdownService, Notifier, RestPagination, RestTable, ServerService } from '@app/core'
9import { AdvancedInputFilter } from '@app/shared/shared-forms'
9import { DropdownAction, Video, VideoService } from '@app/shared/shared-main' 10import { DropdownAction, Video, VideoService } from '@app/shared/shared-main'
10import { VideoBlockService } from '@app/shared/shared-moderation' 11import { VideoBlockService } from '@app/shared/shared-moderation'
11import { VideoBlacklist, VideoBlacklistType } from '@shared/models' 12import { VideoBlacklist, VideoBlacklistType } from '@shared/models'
@@ -24,6 +25,17 @@ export class VideoBlockListComponent extends RestTable implements OnInit, AfterV
24 25
25 videoBlocklistActions: DropdownAction<VideoBlacklist>[][] = [] 26 videoBlocklistActions: DropdownAction<VideoBlacklist>[][] = []
26 27
28 inputFilters: AdvancedInputFilter[] = [
29 {
30 queryParams: { 'search': 'type:auto' },
31 label: $localize`Automatic blocks`
32 },
33 {
34 queryParams: { 'search': 'type:manual' },
35 label: $localize`Manual blocks`
36 }
37 ]
38
27 constructor ( 39 constructor (
28 protected route: ActivatedRoute, 40 protected route: ActivatedRoute,
29 protected router: Router, 41 protected router: Router,
@@ -111,25 +123,6 @@ export class VideoBlockListComponent extends RestTable implements OnInit, AfterV
111 if (this.search) this.setTableFilter(this.search, false) 123 if (this.search) this.setTableFilter(this.search, false)
112 } 124 }
113 125
114 /* Table filter functions */
115 onBlockSearch (event: Event) {
116 this.onSearch(event)
117 this.setQueryParams((event.target as HTMLInputElement).value)
118 }
119
120 setQueryParams (search: string) {
121 const queryParams: Params = {}
122 if (search) Object.assign(queryParams, { search })
123 this.router.navigate([ '/admin/moderation/video-blocks/list' ], { queryParams })
124 }
125
126 resetTableFilter () {
127 this.setTableFilter('')
128 this.setQueryParams('')
129 this.resetSearch()
130 }
131 /* END Table filter functions */
132
133 getIdentifier () { 126 getIdentifier () {
134 return 'VideoBlockListComponent' 127 return 'VideoBlockListComponent'
135 } 128 }
diff --git a/client/src/app/+admin/moderation/video-comment-list/video-comment-list.component.html b/client/src/app/+admin/moderation/video-comment-list/video-comment-list.component.html
index b6cec9c51..5cc0ff137 100644
--- a/client/src/app/+admin/moderation/video-comment-list/video-comment-list.component.html
+++ b/client/src/app/+admin/moderation/video-comment-list/video-comment-list.component.html
@@ -26,25 +26,7 @@
26 </div> 26 </div>
27 27
28 <div class="ml-auto"> 28 <div class="ml-auto">
29 <div class="input-group has-feedback has-clear"> 29 <my-advanced-input-filter [filters]="inputFilters" (search)="onSearch($event)" (resetTableFilter)="resetTableFilter()"></my-advanced-input-filter>
30 <div class="input-group-prepend c-hand" ngbDropdown placement="bottom-left auto" container="body">
31 <div class="input-group-text" ngbDropdownToggle>
32 <span class="caret" aria-haspopup="menu" role="button"></span>
33 </div>
34
35 <div role="menu" ngbDropdownMenu>
36 <h6 class="dropdown-header" i18n>Advanced comments filters</h6>
37 <a [routerLink]="[ '/admin/moderation/video-comments/list' ]" [queryParams]="{ 'search': 'local:true' }" class="dropdown-item" i18n>Local comments</a>
38 <a [routerLink]="[ '/admin/moderation/video-comments/list' ]" [queryParams]="{ 'search': 'local:false' }" class="dropdown-item" i18n>Remote comments</a>
39 </div>
40 </div>
41 <input
42 type="text" name="table-filter" id="table-filter" i18n-placeholder placeholder="Filter..."
43 (keyup)="onSearch($event)"
44 >
45 <a class="glyphicon glyphicon-remove-sign form-control-feedback form-control-clear" (click)="resetTableFilter()"></a>
46 <span class="sr-only" i18n>Clear filters</span>
47 </div>
48 </div> 30 </div>
49 </div> 31 </div>
50 </ng-template> 32 </ng-template>
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 63493d00d..ebbbddb43 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
@@ -2,6 +2,7 @@ import { SortMeta } from 'primeng/api'
2import { AfterViewInit, Component, OnInit } from '@angular/core' 2import { AfterViewInit, Component, OnInit } from '@angular/core'
3import { ActivatedRoute, Router } from '@angular/router' 3import { ActivatedRoute, Router } from '@angular/router'
4import { AuthService, ConfirmService, MarkdownService, Notifier, RestPagination, RestTable } from '@app/core' 4import { AuthService, ConfirmService, MarkdownService, Notifier, RestPagination, RestTable } from '@app/core'
5import { AdvancedInputFilter } from '@app/shared/shared-forms'
5import { DropdownAction } from '@app/shared/shared-main' 6import { DropdownAction } from '@app/shared/shared-main'
6import { BulkService } from '@app/shared/shared-moderation' 7import { BulkService } from '@app/shared/shared-moderation'
7import { VideoCommentAdmin, VideoCommentService } from '@app/shared/shared-video-comment' 8import { VideoCommentAdmin, VideoCommentService } from '@app/shared/shared-video-comment'
@@ -43,6 +44,17 @@ export class VideoCommentListComponent extends RestTable implements OnInit, Afte
43 selectedComments: VideoCommentAdmin[] = [] 44 selectedComments: VideoCommentAdmin[] = []
44 bulkCommentActions: DropdownAction<VideoCommentAdmin[]>[] = [] 45 bulkCommentActions: DropdownAction<VideoCommentAdmin[]>[] = []
45 46
47 inputFilters: AdvancedInputFilter[] = [
48 {
49 queryParams: { 'search': 'local:true' },
50 label: $localize`Local comments`
51 },
52 {
53 queryParams: { 'search': 'local:false' },
54 label: $localize`Remote comments`
55 }
56 ]
57
46 get authUser () { 58 get authUser () {
47 return this.auth.getUser() 59 return this.auth.getUser()
48 } 60 }
diff --git a/client/src/app/+admin/users/user-list/user-list.component.html b/client/src/app/+admin/users/user-list/user-list.component.html
index f84d3fd0c..7170d7019 100644
--- a/client/src/app/+admin/users/user-list/user-list.component.html
+++ b/client/src/app/+admin/users/user-list/user-list.component.html
@@ -22,24 +22,7 @@
22 </div> 22 </div>
23 23
24 <div class="ml-auto"> 24 <div class="ml-auto">
25 <div class="input-group has-feedback has-clear"> 25 <my-advanced-input-filter [filters]="inputFilters" (search)="onSearch($event)" (resetTableFilter)="resetTableFilter()"></my-advanced-input-filter>
26 <div class="input-group-prepend c-hand" ngbDropdown placement="bottom-left auto" container="body">
27 <div class="input-group-text" ngbDropdownToggle>
28 <span class="caret" aria-haspopup="menu" role="button"></span>
29 </div>
30
31 <div role="menu" ngbDropdownMenu>
32 <h6 class="dropdown-header" i18n>Advanced user filters</h6>
33 <a [routerLink]="[ '/admin/users/list' ]" [queryParams]="{ 'search': 'banned:true' }" class="dropdown-item" i18n>Banned users</a>
34 </div>
35 </div>
36 <input
37 type="text" name="table-filter" id="table-filter" i18n-placeholder placeholder="Filter..."
38 (keyup)="onSearch($event)"
39 >
40 <a class="glyphicon glyphicon-remove-sign form-control-feedback form-control-clear" (click)="resetTableFilter()"></a>
41 <span class="sr-only" i18n>Clear filters</span>
42 </div>
43 </div> 26 </div>
44 27
45 </div> 28 </div>
diff --git a/client/src/app/+admin/users/user-list/user-list.component.scss b/client/src/app/+admin/users/user-list/user-list.component.scss
index f18747ec3..db4979a51 100644
--- a/client/src/app/+admin/users/user-list/user-list.component.scss
+++ b/client/src/app/+admin/users/user-list/user-list.component.scss
@@ -11,6 +11,7 @@ tr.banned > td {
11 11
12.table-email { 12.table-email {
13 @include disable-default-a-behaviour; 13 @include disable-default-a-behaviour;
14
14 color: pvar(--mainForegroundColor); 15 color: pvar(--mainForegroundColor);
15} 16}
16 17
@@ -28,14 +29,6 @@ tr.banned > td {
28 margin-left: 0.1rem; 29 margin-left: 0.1rem;
29} 30}
30 31
31.caption {
32 justify-content: space-between;
33
34 input {
35 @include peertube-input-text(250px);
36 }
37}
38
39p-tableCheckbox { 32p-tableCheckbox {
40 position: relative; 33 position: relative;
41 top: -2.5px; 34 top: -2.5px;
@@ -55,18 +48,7 @@ my-global-icon {
55 48
56.progress { 49.progress {
57 @include progressbar($small: true); 50 @include progressbar($small: true);
51
58 width: auto; 52 width: auto;
59 max-width: 100%; 53 max-width: 100%;
60} 54}
61
62.input-group {
63 @include peertube-input-group(300px);
64
65 input {
66 flex: 1;
67 }
68
69 .dropdown-toggle::after {
70 margin-left: 0;
71 }
72}
diff --git a/client/src/app/+admin/users/user-list/user-list.component.ts b/client/src/app/+admin/users/user-list/user-list.component.ts
index 339e18206..435bc17d7 100644
--- a/client/src/app/+admin/users/user-list/user-list.component.ts
+++ b/client/src/app/+admin/users/user-list/user-list.component.ts
@@ -1,8 +1,9 @@
1import { SortMeta } from 'primeng/api' 1import { SortMeta } from 'primeng/api'
2import { Component, OnInit, ViewChild } from '@angular/core' 2import { AfterViewInit, Component, OnInit, ViewChild } from '@angular/core'
3import { ActivatedRoute, Params, Router } from '@angular/router' 3import { ActivatedRoute, Router } from '@angular/router'
4import { AuthService, ConfirmService, Notifier, RestPagination, RestTable, ServerService, UserService } from '@app/core' 4import { AuthService, ConfirmService, Notifier, RestPagination, RestTable, ServerService, UserService } from '@app/core'
5import { Account, DropdownAction } from '@app/shared/shared-main' 5import { AdvancedInputFilter } from '@app/shared/shared-forms'
6import { DropdownAction } from '@app/shared/shared-main'
6import { UserBanModalComponent } from '@app/shared/shared-moderation' 7import { UserBanModalComponent } from '@app/shared/shared-moderation'
7import { ServerConfig, User, UserRole } from '@shared/models' 8import { ServerConfig, User, UserRole } from '@shared/models'
8 9
@@ -18,19 +19,28 @@ type UserForList = User & {
18 templateUrl: './user-list.component.html', 19 templateUrl: './user-list.component.html',
19 styleUrls: [ './user-list.component.scss' ] 20 styleUrls: [ './user-list.component.scss' ]
20}) 21})
21export class UserListComponent extends RestTable implements OnInit { 22export class UserListComponent extends RestTable implements OnInit, AfterViewInit {
22 @ViewChild('userBanModal', { static: true }) userBanModal: UserBanModalComponent 23 @ViewChild('userBanModal', { static: true }) userBanModal: UserBanModalComponent
23 24
24 users: User[] = [] 25 users: User[] = []
26
25 totalRecords = 0 27 totalRecords = 0
26 sort: SortMeta = { field: 'createdAt', order: 1 } 28 sort: SortMeta = { field: 'createdAt', order: 1 }
27 pagination: RestPagination = { count: this.rowsPerPage, start: 0 } 29 pagination: RestPagination = { count: this.rowsPerPage, start: 0 }
30
28 highlightBannedUsers = false 31 highlightBannedUsers = false
29 32
30 selectedUsers: User[] = [] 33 selectedUsers: User[] = []
31 bulkUserActions: DropdownAction<User[]>[][] = [] 34 bulkUserActions: DropdownAction<User[]>[][] = []
32 columns: { id: string, label: string }[] 35 columns: { id: string, label: string }[]
33 36
37 inputFilters: AdvancedInputFilter[] = [
38 {
39 queryParams: { 'search': 'banned:true' },
40 label: $localize`Banned users`
41 }
42 ]
43
34 private _selectedColumns: string[] 44 private _selectedColumns: string[]
35 private serverConfig: ServerConfig 45 private serverConfig: ServerConfig
36 46
@@ -117,6 +127,10 @@ export class UserListComponent extends RestTable implements OnInit {
117 this.columns.push({ id: 'lastLoginDate', label: 'Last login' }) 127 this.columns.push({ id: 'lastLoginDate', label: 'Last login' })
118 } 128 }
119 129
130 ngAfterViewInit () {
131 if (this.search) this.setTableFilter(this.search, false)
132 }
133
120 getIdentifier () { 134 getIdentifier () {
121 return 'UserListComponent' 135 return 'UserListComponent'
122 } 136 }
diff --git a/client/src/app/+my-account/my-account-abuses/my-account-abuses-list.component.html b/client/src/app/+my-account/my-account-abuses/my-account-abuses-list.component.html
index 59ca61be6..e83b59019 100644
--- a/client/src/app/+my-account/my-account-abuses/my-account-abuses-list.component.html
+++ b/client/src/app/+my-account/my-account-abuses/my-account-abuses-list.component.html
@@ -3,4 +3,4 @@
3 <ng-container i18n>Reports</ng-container> 3 <ng-container i18n>Reports</ng-container>
4</h1> 4</h1>
5 5
6<my-abuse-list-table viewType="user" baseRoute="/my-account/abuses"></my-abuse-list-table> 6<my-abuse-list-table viewType="user"></my-abuse-list-table>
diff --git a/client/src/app/+my-library/my-videos/my-videos.component.html b/client/src/app/+my-library/my-videos/my-videos.component.html
index e9f436378..7c1cdb511 100644
--- a/client/src/app/+my-library/my-videos/my-videos.component.html
+++ b/client/src/app/+my-library/my-videos/my-videos.component.html
@@ -19,12 +19,7 @@
19</h1> 19</h1>
20 20
21<div class="videos-header d-flex justify-content-between"> 21<div class="videos-header d-flex justify-content-between">
22 <div class="has-feedback has-clear"> 22 <my-advanced-input-filter [filters]="inputFilters" (search)="onSearch($event)" (resetTableFilter)="resetTableFilter()"></my-advanced-input-filter>
23 <input type="text" placeholder="Search your videos" i18n-placeholder [(ngModel)]="videosSearch"
24 (ngModelChange)="onVideosSearchChanged()" />
25 <a class="glyphicon glyphicon-remove-sign form-control-feedback form-control-clear" (click)="resetSearch()"></a>
26 <span class="sr-only" i18n>Clear filters</span>
27 </div>
28 23
29 <div class="peertube-select-container peertube-select-button"> 24 <div class="peertube-select-container peertube-select-button">
30 <select [(ngModel)]="sort" (ngModelChange)="onChangeSortColumn()" class="form-control"> 25 <select [(ngModel)]="sort" (ngModelChange)="onChangeSortColumn()" class="form-control">
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 356e158d6..f9c1b32b0 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
@@ -1,10 +1,11 @@
1import { concat, Observable, Subject } from 'rxjs' 1import { concat, Observable } from 'rxjs'
2import { debounceTime, tap, toArray } from 'rxjs/operators' 2import { tap, toArray } from 'rxjs/operators'
3import { Component, OnInit, ViewChild } from '@angular/core' 3import { AfterViewInit, Component, OnInit, ViewChild } from '@angular/core'
4import { ActivatedRoute, Router } from '@angular/router' 4import { ActivatedRoute, Router } from '@angular/router'
5import { AuthService, ComponentPagination, ConfirmService, Notifier, ScreenService, ServerService, User } from '@app/core' 5import { AuthService, ComponentPagination, ConfirmService, Notifier, RouteFilter, ScreenService, ServerService, User } from '@app/core'
6import { DisableForReuseHook } from '@app/core/routing/disable-for-reuse-hook' 6import { DisableForReuseHook } from '@app/core/routing/disable-for-reuse-hook'
7import { immutableAssign } from '@app/helpers' 7import { immutableAssign } from '@app/helpers'
8import { AdvancedInputFilter } from '@app/shared/shared-forms'
8import { DropdownAction, Video, VideoService } from '@app/shared/shared-main' 9import { DropdownAction, Video, VideoService } from '@app/shared/shared-main'
9import { LiveStreamInformationComponent } from '@app/shared/shared-video-live' 10import { LiveStreamInformationComponent } from '@app/shared/shared-video-live'
10import { MiniatureDisplayOptions, SelectionType, VideosSelectionComponent } from '@app/shared/shared-video-miniature' 11import { MiniatureDisplayOptions, SelectionType, VideosSelectionComponent } from '@app/shared/shared-video-miniature'
@@ -15,7 +16,7 @@ import { VideoChangeOwnershipComponent } from './modals/video-change-ownership.c
15 templateUrl: './my-videos.component.html', 16 templateUrl: './my-videos.component.html',
16 styleUrls: [ './my-videos.component.scss' ] 17 styleUrls: [ './my-videos.component.scss' ]
17}) 18})
18export class MyVideosComponent implements OnInit, DisableForReuseHook { 19export class MyVideosComponent extends RouteFilter implements OnInit, AfterViewInit, DisableForReuseHook {
19 @ViewChild('videosSelection', { static: true }) videosSelection: VideosSelectionComponent 20 @ViewChild('videosSelection', { static: true }) videosSelection: VideosSelectionComponent
20 @ViewChild('videoChangeOwnershipModal', { static: true }) videoChangeOwnershipModal: VideoChangeOwnershipComponent 21 @ViewChild('videoChangeOwnershipModal', { static: true }) videoChangeOwnershipModal: VideoChangeOwnershipComponent
21 @ViewChild('liveStreamInformationModal', { static: true }) liveStreamInformationModal: LiveStreamInformationComponent 22 @ViewChild('liveStreamInformationModal', { static: true }) liveStreamInformationModal: LiveStreamInformationComponent
@@ -40,13 +41,18 @@ export class MyVideosComponent implements OnInit, DisableForReuseHook {
40 videoActions: DropdownAction<{ video: Video }>[] = [] 41 videoActions: DropdownAction<{ video: Video }>[] = []
41 42
42 videos: Video[] = [] 43 videos: Video[] = []
43 videosSearch: string
44 videosSearchChanged = new Subject<string>()
45 getVideosObservableFunction = this.getVideosObservable.bind(this) 44 getVideosObservableFunction = this.getVideosObservable.bind(this)
46 sort: VideoSortField = '-publishedAt' 45 sort: VideoSortField = '-publishedAt'
47 46
48 user: User 47 user: User
49 48
49 inputFilters: AdvancedInputFilter[] = [
50 {
51 queryParams: { 'search': 'isLive:true' },
52 label: $localize`Only live videos`
53 }
54 ]
55
50 constructor ( 56 constructor (
51 protected router: Router, 57 protected router: Router,
52 protected serverService: ServerService, 58 protected serverService: ServerService,
@@ -57,6 +63,8 @@ export class MyVideosComponent implements OnInit, DisableForReuseHook {
57 private confirmService: ConfirmService, 63 private confirmService: ConfirmService,
58 private videoService: VideoService 64 private videoService: VideoService
59 ) { 65 ) {
66 super()
67
60 this.titlePage = $localize`My videos` 68 this.titlePage = $localize`My videos`
61 } 69 }
62 70
@@ -65,20 +73,16 @@ export class MyVideosComponent implements OnInit, DisableForReuseHook {
65 73
66 this.user = this.authService.getUser() 74 this.user = this.authService.getUser()
67 75
68 this.videosSearchChanged 76 this.initSearch()
69 .pipe(debounceTime(500)) 77 this.listenToSearchChange()
70 .subscribe(() => {
71 this.videosSelection.reloadVideos()
72 })
73 } 78 }
74 79
75 resetSearch () { 80 ngAfterViewInit () {
76 this.videosSearch = '' 81 if (this.search) this.setTableFilter(this.search, false)
77 this.onVideosSearchChanged()
78 } 82 }
79 83
80 onVideosSearchChanged () { 84 loadData () {
81 this.videosSearchChanged.next() 85 this.videosSelection.reloadVideos()
82 } 86 }
83 87
84 onChangeSortColumn () { 88 onChangeSortColumn () {
@@ -96,7 +100,7 @@ export class MyVideosComponent implements OnInit, DisableForReuseHook {
96 getVideosObservable (page: number) { 100 getVideosObservable (page: number) {
97 const newPagination = immutableAssign(this.pagination, { currentPage: page }) 101 const newPagination = immutableAssign(this.pagination, { currentPage: page })
98 102
99 return this.videoService.getMyVideos(newPagination, this.sort, this.videosSearch) 103 return this.videoService.getMyVideos(newPagination, this.sort, this.search)
100 .pipe( 104 .pipe(
101 tap(res => this.pagination.totalItems = res.total) 105 tap(res => this.pagination.totalItems = res.total)
102 ) 106 )
diff --git a/client/src/app/core/rest/rest-table.ts b/client/src/app/core/rest/rest-table.ts
index 32c1db446..9baab8a39 100644
--- a/client/src/app/core/rest/rest-table.ts
+++ b/client/src/app/core/rest/rest-table.ts
@@ -1,14 +1,14 @@
1import * as debug from 'debug' 1import * as debug from 'debug'
2import { LazyLoadEvent, SortMeta } from 'primeng/api' 2import { LazyLoadEvent, SortMeta } from 'primeng/api'
3import { Subject } from 'rxjs' 3import { Subject } from 'rxjs'
4import { debounceTime, distinctUntilChanged } from 'rxjs/operators' 4import { ActivatedRoute, Router } from '@angular/router'
5import { ActivatedRoute, Params, Router } from '@angular/router'
6import { peertubeLocalStorage } from '@root-helpers/peertube-web-storage' 5import { peertubeLocalStorage } from '@root-helpers/peertube-web-storage'
6import { RouteFilter } from '../routing'
7import { RestPagination } from './rest-pagination' 7import { RestPagination } from './rest-pagination'
8 8
9const logger = debug('peertube:tables:RestTable') 9const logger = debug('peertube:tables:RestTable')
10 10
11export abstract class RestTable { 11export abstract class RestTable extends RouteFilter {
12 12
13 abstract totalRecords: number 13 abstract totalRecords: number
14 abstract sort: SortMeta 14 abstract sort: SortMeta
@@ -19,8 +19,6 @@ export abstract class RestTable {
19 rowsPerPage = this.rowsPerPageOptions[0] 19 rowsPerPage = this.rowsPerPageOptions[0]
20 expandedRows = {} 20 expandedRows = {}
21 21
22 baseRoute: string
23
24 protected searchStream: Subject<string> 22 protected searchStream: Subject<string>
25 23
26 protected route: ActivatedRoute 24 protected route: ActivatedRoute
@@ -66,55 +64,6 @@ export abstract class RestTable {
66 peertubeLocalStorage.setItem(this.getSortLocalStorageKey(), JSON.stringify(this.sort)) 64 peertubeLocalStorage.setItem(this.getSortLocalStorageKey(), JSON.stringify(this.sort))
67 } 65 }
68 66
69 initSearch () {
70 this.searchStream = new Subject()
71
72 this.searchStream
73 .pipe(
74 debounceTime(400),
75 distinctUntilChanged()
76 )
77 .subscribe(search => {
78 this.search = search
79
80 logger('On search %s.', this.search)
81
82 this.loadData()
83 })
84 }
85
86 onSearch (event: Event) {
87 const target = event.target as HTMLInputElement
88 this.searchStream.next(target.value)
89
90 this.setQueryParams((event.target as HTMLInputElement).value)
91 }
92
93 setQueryParams (search: string) {
94 if (!this.baseRoute) return
95
96 const queryParams: Params = {}
97
98 if (search) Object.assign(queryParams, { search })
99 this.router.navigate([ this.baseRoute ], { queryParams })
100 }
101
102 resetTableFilter () {
103 this.setTableFilter('')
104 this.setQueryParams('')
105 this.resetSearch()
106 }
107
108 listenToSearchChange () {
109 this.route.queryParams
110 .subscribe(params => {
111 this.search = params.search || ''
112
113 // Primeng table will run an event to load data
114 this.setTableFilter(this.search)
115 })
116 }
117
118 onPage (event: { first: number, rows: number }) { 67 onPage (event: { first: number, rows: number }) {
119 logger('On page %o.', event) 68 logger('On page %o.', event)
120 69
@@ -131,21 +80,6 @@ export abstract class RestTable {
131 this.expandedRows = {} 80 this.expandedRows = {}
132 } 81 }
133 82
134 setTableFilter (filter: string, triggerEvent = true) {
135 // FIXME: cannot use ViewChild, so create a component for the filter input
136 const filterInput = document.getElementById('table-filter') as HTMLInputElement
137 if (!filterInput) return
138
139 filterInput.value = filter
140
141 if (triggerEvent) filterInput.dispatchEvent(new Event('keyup'))
142 }
143
144 resetSearch () {
145 this.searchStream.next('')
146 this.setTableFilter('')
147 }
148
149 protected abstract loadData (): void 83 protected abstract loadData (): void
150 84
151 private getSortLocalStorageKey () { 85 private getSortLocalStorageKey () {
diff --git a/client/src/app/core/routing/index.ts b/client/src/app/core/routing/index.ts
index 239c27caf..d53a4ae2c 100644
--- a/client/src/app/core/routing/index.ts
+++ b/client/src/app/core/routing/index.ts
@@ -5,6 +5,7 @@ export * from './login-guard.service'
5export * from './menu-guard.service' 5export * from './menu-guard.service'
6export * from './preload-selected-modules-list' 6export * from './preload-selected-modules-list'
7export * from './redirect.service' 7export * from './redirect.service'
8export * from './route-filter'
8export * from './server-config-resolver.service' 9export * from './server-config-resolver.service'
9export * from './unlogged-guard.service' 10export * from './unlogged-guard.service'
10export * from './user-right-guard.service' 11export * from './user-right-guard.service'
diff --git a/client/src/app/core/routing/route-filter.ts b/client/src/app/core/routing/route-filter.ts
new file mode 100644
index 000000000..f783a0c40
--- /dev/null
+++ b/client/src/app/core/routing/route-filter.ts
@@ -0,0 +1,79 @@
1import * as debug from 'debug'
2import { Subject } from 'rxjs'
3import { debounceTime, distinctUntilChanged } from 'rxjs/operators'
4import { ActivatedRoute, Params, Router } from '@angular/router'
5
6const logger = debug('peertube:tables:RouteFilter')
7
8export abstract class RouteFilter {
9 search: string
10
11 protected searchStream: Subject<string>
12
13 protected route: ActivatedRoute
14 protected router: Router
15
16 initSearch () {
17 this.searchStream = new Subject()
18
19 this.searchStream
20 .pipe(
21 debounceTime(400),
22 distinctUntilChanged()
23 )
24 .subscribe(search => {
25 this.search = search
26
27 logger('On search %s.', this.search)
28
29 this.loadData()
30 })
31 }
32
33 onSearch (event: Event) {
34 const target = event.target as HTMLInputElement
35 this.searchStream.next(target.value)
36
37 this.setQueryParams(target.value)
38 }
39
40 resetTableFilter () {
41 this.setTableFilter('')
42 this.setQueryParams('')
43 this.resetSearch()
44 }
45
46 resetSearch () {
47 this.searchStream.next('')
48 this.setTableFilter('')
49 }
50
51 listenToSearchChange () {
52 this.route.queryParams
53 .subscribe(params => {
54 this.search = params.search || ''
55
56 // Primeng table will run an event to load data
57 this.setTableFilter(this.search)
58 })
59 }
60
61 setTableFilter (filter: string, triggerEvent = true) {
62 // FIXME: cannot use ViewChild, so create a component for the filter input
63 const filterInput = document.getElementById('table-filter') as HTMLInputElement
64 if (!filterInput) return
65
66 filterInput.value = filter
67
68 if (triggerEvent) filterInput.dispatchEvent(new Event('keyup'))
69 }
70
71 protected abstract loadData (): void
72
73 private setQueryParams (search: string) {
74 const queryParams: Params = {}
75
76 if (search) Object.assign(queryParams, { search })
77 this.router.navigate([ ], { queryParams })
78 }
79}
diff --git a/client/src/app/shared/shared-abuse-list/abuse-details.component.html b/client/src/app/shared/shared-abuse-list/abuse-details.component.html
index f2eaeb32f..110f574fa 100644
--- a/client/src/app/shared/shared-abuse-list/abuse-details.component.html
+++ b/client/src/app/shared/shared-abuse-list/abuse-details.component.html
@@ -7,7 +7,7 @@
7 <span class="col-3 moderation-expanded-label" i18n>Reporter</span> 7 <span class="col-3 moderation-expanded-label" i18n>Reporter</span>
8 8
9 <span class="col-9 moderation-expanded-text"> 9 <span class="col-9 moderation-expanded-text">
10 <a [routerLink]="[ baseRoute ]" [queryParams]="{ 'search': 'reporter:&quot;' + abuse.reporterAccount.displayName + '&quot;' }" 10 <a [routerLink]="[ '.' ]" [queryParams]="{ 'search': 'reporter:&quot;' + abuse.reporterAccount.displayName + '&quot;' }"
11 class="chip" 11 class="chip"
12 > 12 >
13 <my-actor-avatar [account]="abuse.reporterAccount"></my-actor-avatar> 13 <my-actor-avatar [account]="abuse.reporterAccount"></my-actor-avatar>
@@ -16,7 +16,7 @@
16 </div> 16 </div>
17 </a> 17 </a>
18 18
19 <a [routerLink]="[ baseRoute ]" [queryParams]="{ 'search': 'reporter:&quot;' + abuse.reporterAccount.displayName + '&quot;' }" 19 <a [routerLink]="[ '.' ]" [queryParams]="{ 'search': 'reporter:&quot;' + abuse.reporterAccount.displayName + '&quot;' }"
20 class="ml-auto text-muted abuse-details-links" i18n 20 class="ml-auto text-muted abuse-details-links" i18n
21 > 21 >
22 {abuse.countReportsForReporter, plural, =1 {1 report} other {{{ abuse.countReportsForReporter }} reports}}<span class="ml-1 glyphicon glyphicon-flag"></span> 22 {abuse.countReportsForReporter, plural, =1 {1 report} other {{{ abuse.countReportsForReporter }} reports}}<span class="ml-1 glyphicon glyphicon-flag"></span>
@@ -27,7 +27,7 @@
27 <div class="d-flex" *ngIf="abuse.flaggedAccount"> 27 <div class="d-flex" *ngIf="abuse.flaggedAccount">
28 <span class="col-3 moderation-expanded-label" i18n>Reportee</span> 28 <span class="col-3 moderation-expanded-label" i18n>Reportee</span>
29 <span class="col-9 moderation-expanded-text"> 29 <span class="col-9 moderation-expanded-text">
30 <a [routerLink]="[ baseRoute ]" [queryParams]="{ 'search': 'reportee:&quot;' +abuse.flaggedAccount.displayName + '&quot;' }" 30 <a [routerLink]="[ '.' ]" [queryParams]="{ 'search': 'reportee:&quot;' +abuse.flaggedAccount.displayName + '&quot;' }"
31 class="chip" 31 class="chip"
32 > 32 >
33 <my-actor-avatar [account]="abuse.flaggedAccount"></my-actor-avatar> 33 <my-actor-avatar [account]="abuse.flaggedAccount"></my-actor-avatar>
@@ -36,7 +36,7 @@
36 </div> 36 </div>
37 </a> 37 </a>
38 38
39 <a *ngIf="isAdminView" [routerLink]="[ baseRoute ]" [queryParams]="{ 'search': 'reportee:&quot;' +abuse.flaggedAccount.displayName + '&quot;' }" 39 <a *ngIf="isAdminView" [routerLink]="[ '.' ]" [queryParams]="{ 'search': 'reportee:&quot;' +abuse.flaggedAccount.displayName + '&quot;' }"
40 class="ml-auto text-muted abuse-details-links" i18n 40 class="ml-auto text-muted abuse-details-links" i18n
41 > 41 >
42 {abuse.countReportsForReportee, plural, =1 {1 report} other {{{ abuse.countReportsForReportee }} reports}}<span class="ml-1 glyphicon glyphicon-flag"></span> 42 {abuse.countReportsForReportee, plural, =1 {1 report} other {{{ abuse.countReportsForReportee }} reports}}<span class="ml-1 glyphicon glyphicon-flag"></span>
@@ -53,7 +53,7 @@
53 <div class="mt-3 d-flex"> 53 <div class="mt-3 d-flex">
54 <span class="col-3 moderation-expanded-label"> 54 <span class="col-3 moderation-expanded-label">
55 <ng-container i18n>Report</ng-container> 55 <ng-container i18n>Report</ng-container>
56 <a [routerLink]="[ baseRoute ]" [queryParams]="{ 'search': '#' + abuse.id }" class="ml-1 text-muted">#{{ abuse.id }}</a> 56 <a [routerLink]="[ '.' ]" [queryParams]="{ 'search': '#' + abuse.id }" class="ml-1 text-muted">#{{ abuse.id }}</a>
57 </span> 57 </span>
58 <span class="col-9 moderation-expanded-text" [innerHTML]="abuse.reasonHtml"></span> 58 <span class="col-9 moderation-expanded-text" [innerHTML]="abuse.reasonHtml"></span>
59 </div> 59 </div>
@@ -61,7 +61,7 @@
61 <div *ngIf="getPredefinedReasons()" class="mt-2 d-flex"> 61 <div *ngIf="getPredefinedReasons()" class="mt-2 d-flex">
62 <span class="col-3"></span> 62 <span class="col-3"></span>
63 <span class="col-9"> 63 <span class="col-9">
64 <a *ngFor="let reason of getPredefinedReasons()" [routerLink]="[ baseRoute ]" 64 <a *ngFor="let reason of getPredefinedReasons()" [routerLink]="[ '.' ]"
65 [queryParams]="{ 'search': 'tag:' + reason.id }" class="chip rectangular bg-secondary text-light" 65 [queryParams]="{ 'search': 'tag:' + reason.id }" class="chip rectangular bg-secondary text-light"
66 > 66 >
67 <div>{{ reason.label }}</div> 67 <div>{{ reason.label }}</div>
diff --git a/client/src/app/shared/shared-abuse-list/abuse-details.component.ts b/client/src/app/shared/shared-abuse-list/abuse-details.component.ts
index e8ce7e678..14674c5f0 100644
--- a/client/src/app/shared/shared-abuse-list/abuse-details.component.ts
+++ b/client/src/app/shared/shared-abuse-list/abuse-details.component.ts
@@ -1,6 +1,5 @@
1import { Component, Input } from '@angular/core' 1import { Component, Input } from '@angular/core'
2import { durationToString } from '@app/helpers' 2import { durationToString } from '@app/helpers'
3import { Account } from '@app/shared/shared-main'
4import { AbusePredefinedReasonsString } from '@shared/models' 3import { AbusePredefinedReasonsString } from '@shared/models'
5import { ProcessedAbuse } from './processed-abuse.model' 4import { ProcessedAbuse } from './processed-abuse.model'
6 5
@@ -12,7 +11,6 @@ import { ProcessedAbuse } from './processed-abuse.model'
12export class AbuseDetailsComponent { 11export class AbuseDetailsComponent {
13 @Input() abuse: ProcessedAbuse 12 @Input() abuse: ProcessedAbuse
14 @Input() isAdminView: boolean 13 @Input() isAdminView: boolean
15 @Input() baseRoute: string
16 14
17 private predefinedReasonsTranslations: { [key in AbusePredefinedReasonsString]: string } 15 private predefinedReasonsTranslations: { [key in AbusePredefinedReasonsString]: string }
18 16
diff --git a/client/src/app/shared/shared-abuse-list/abuse-list-table.component.html b/client/src/app/shared/shared-abuse-list/abuse-list-table.component.html
index b41bc75d4..22f84a96e 100644
--- a/client/src/app/shared/shared-abuse-list/abuse-list-table.component.html
+++ b/client/src/app/shared/shared-abuse-list/abuse-list-table.component.html
@@ -8,28 +8,7 @@
8 <ng-template pTemplate="caption"> 8 <ng-template pTemplate="caption">
9 <div class="caption"> 9 <div class="caption">
10 <div class="ml-auto"> 10 <div class="ml-auto">
11 <div class="input-group has-feedback has-clear"> 11 <my-advanced-input-filter [filters]="inputFilters" (search)="onSearch($event)" (resetTableFilter)="resetTableFilter()"></my-advanced-input-filter>
12 <div class="input-group-prepend c-hand" ngbDropdown placement="bottom-left auto" container="body">
13 <div class="input-group-text" ngbDropdownToggle>
14 <span class="caret" aria-haspopup="menu" role="button"></span>
15 </div>
16
17 <div role="menu" ngbDropdownMenu>
18 <h6 class="dropdown-header" i18n>Advanced report filters</h6>
19 <a [routerLink]="[ baseRoute ]" [queryParams]="{ 'search': 'state:pending' }" class="dropdown-item" i18n>Unsolved reports</a>
20 <a [routerLink]="[ baseRoute ]" [queryParams]="{ 'search': 'state:accepted' }" class="dropdown-item" i18n>Accepted reports</a>
21 <a [routerLink]="[ baseRoute ]" [queryParams]="{ 'search': 'state:rejected' }" class="dropdown-item" i18n>Refused reports</a>
22 <a [routerLink]="[ baseRoute ]" [queryParams]="{ 'search': 'videoIs:blacklisted' }" class="dropdown-item" i18n>Reports with blocked videos</a>
23 <a [routerLink]="[ baseRoute ]" [queryParams]="{ 'search': 'videoIs:deleted' }" class="dropdown-item" i18n>Reports with deleted videos</a>
24 </div>
25 </div>
26 <input
27 type="text" name="table-filter" id="table-filter" i18n-placeholder placeholder="Filter..."
28 (keyup)="onSearch($event)"
29 >
30 <a class="glyphicon glyphicon-remove-sign form-control-feedback form-control-clear" (click)="resetTableFilter()"></a>
31 <span class="sr-only" i18n>Clear filters</span>
32 </div>
33 </div> 12 </div>
34 </div> 13 </div>
35 </ng-template> 14 </ng-template>
@@ -171,7 +150,7 @@
171 <ng-template pTemplate="rowexpansion" let-abuse> 150 <ng-template pTemplate="rowexpansion" let-abuse>
172 <tr> 151 <tr>
173 <td class="expand-cell" colspan="8"> 152 <td class="expand-cell" colspan="8">
174 <my-abuse-details [abuse]="abuse" [baseRoute]="baseRoute" [isAdminView]="isAdminView()"></my-abuse-details> 153 <my-abuse-details [abuse]="abuse" [isAdminView]="isAdminView()"></my-abuse-details>
175 </td> 154 </td>
176 </tr> 155 </tr>
177 </ng-template> 156 </ng-template>
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 8b5771237..f393c0d1e 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
@@ -14,6 +14,7 @@ import { AbuseState, AdminAbuse } from '@shared/models'
14import { AbuseMessageModalComponent } from './abuse-message-modal.component' 14import { AbuseMessageModalComponent } from './abuse-message-modal.component'
15import { ModerationCommentModalComponent } from './moderation-comment-modal.component' 15import { ModerationCommentModalComponent } from './moderation-comment-modal.component'
16import { ProcessedAbuse } from './processed-abuse.model' 16import { ProcessedAbuse } from './processed-abuse.model'
17import { AdvancedInputFilter } from '../shared-forms'
17 18
18const logger = debug('peertube:moderation:AbuseListTableComponent') 19const logger = debug('peertube:moderation:AbuseListTableComponent')
19 20
@@ -24,7 +25,6 @@ const logger = debug('peertube:moderation:AbuseListTableComponent')
24}) 25})
25export class AbuseListTableComponent extends RestTable implements OnInit, AfterViewInit { 26export class AbuseListTableComponent extends RestTable implements OnInit, AfterViewInit {
26 @Input() viewType: 'admin' | 'user' 27 @Input() viewType: 'admin' | 'user'
27 @Input() baseRoute: string
28 28
29 @ViewChild('abuseMessagesModal', { static: true }) abuseMessagesModal: AbuseMessageModalComponent 29 @ViewChild('abuseMessagesModal', { static: true }) abuseMessagesModal: AbuseMessageModalComponent
30 @ViewChild('moderationCommentModal', { static: true }) moderationCommentModal: ModerationCommentModalComponent 30 @ViewChild('moderationCommentModal', { static: true }) moderationCommentModal: ModerationCommentModalComponent
@@ -36,6 +36,29 @@ export class AbuseListTableComponent extends RestTable implements OnInit, AfterV
36 36
37 abuseActions: DropdownAction<ProcessedAbuse>[][] = [] 37 abuseActions: DropdownAction<ProcessedAbuse>[][] = []
38 38
39 inputFilters: AdvancedInputFilter[] = [
40 {
41 queryParams: { 'search': 'state:pending' },
42 label: $localize`Unsolved reports`
43 },
44 {
45 queryParams: { 'search': 'state:accepted' },
46 label: $localize`Accepted reports`
47 },
48 {
49 queryParams: { 'search': 'state:rejected' },
50 label: $localize`Refused reports`
51 },
52 {
53 queryParams: { 'search': 'videoIs:blacklisted' },
54 label: $localize`Reports with blocked videos`
55 },
56 {
57 queryParams: { 'search': 'videoIs:deleted' },
58 label: $localize`Reports with deleted videos`
59 }
60 ]
61
39 constructor ( 62 constructor (
40 protected route: ActivatedRoute, 63 protected route: ActivatedRoute,
41 protected router: Router, 64 protected router: Router,
diff --git a/client/src/app/shared/shared-actor-image/actor-avatar.component.ts b/client/src/app/shared/shared-actor-image/actor-avatar.component.ts
index e4efbe475..835e15110 100644
--- a/client/src/app/shared/shared-actor-image/actor-avatar.component.ts
+++ b/client/src/app/shared/shared-actor-image/actor-avatar.component.ts
@@ -98,7 +98,7 @@ export class ActorAvatarComponent {
98 jkl: 'gray', 98 jkl: 'gray',
99 mno: 'yellow', 99 mno: 'yellow',
100 pqr: 'orange', 100 pqr: 'orange',
101 stv: 'red', 101 stvu: 'red',
102 wxyz: 'dark-blue' 102 wxyz: 'dark-blue'
103 } 103 }
104 104
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
new file mode 100644
index 000000000..03c4f127b
--- /dev/null
+++ b/client/src/app/shared/shared-forms/advanced-input-filter.component.html
@@ -0,0 +1,22 @@
1<div class="input-group has-feedback has-clear">
2 <div class="input-group-prepend c-hand" ngbDropdown placement="bottom-left auto" container="body">
3 <div class="input-group-text" ngbDropdownToggle>
4 <span class="caret" aria-haspopup="menu" role="button"></span>
5 </div>
6
7 <div role="menu" ngbDropdownMenu>
8 <h6 class="dropdown-header" i18n>Advanced filters</h6>
9
10 <a *ngFor="let filter of filters" [routerLink]="[ '.' ]" [queryParams]="filter.queryParams" class="dropdown-item">
11 {{ filter.label }}
12 </a>
13
14 </div>
15 </div>
16 <input
17 type="text" name="table-filter" id="table-filter" i18n-placeholder placeholder="Filter..."
18 (keyup)="onSearch($event)"
19 >
20 <a class="glyphicon glyphicon-remove-sign form-control-feedback form-control-clear" (click)="onResetTableFilter()"></a>
21 <span class="sr-only" i18n>Clear filters</span>
22</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
new file mode 100644
index 000000000..7c2198927
--- /dev/null
+++ b/client/src/app/shared/shared-forms/advanced-input-filter.component.scss
@@ -0,0 +1,10 @@
1@import '_variables';
2@import '_mixins';
3
4input {
5 @include peertube-input-text(250px);
6}
7
8.input-group-text {
9 background-color: transparent;
10}
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
new file mode 100644
index 000000000..394090751
--- /dev/null
+++ b/client/src/app/shared/shared-forms/advanced-input-filter.component.ts
@@ -0,0 +1,27 @@
1import { Component, EventEmitter, Input, Output } from '@angular/core'
2import { Params } from '@angular/router'
3
4export type AdvancedInputFilter = {
5 label: string
6 queryParams: Params
7}
8
9@Component({
10 selector: 'my-advanced-input-filter',
11 templateUrl: './advanced-input-filter.component.html',
12 styleUrls: [ './advanced-input-filter.component.scss' ]
13})
14export class AdvancedInputFilterComponent {
15 @Input() filters: AdvancedInputFilter[] = []
16
17 @Output() resetTableFilter = new EventEmitter<void>()
18 @Output() search = new EventEmitter<Event>()
19
20 onSearch (event: Event) {
21 this.search.emit(event)
22 }
23
24 onResetTableFilter () {
25 this.resetTableFilter.emit()
26 }
27}
diff --git a/client/src/app/shared/shared-forms/index.ts b/client/src/app/shared/shared-forms/index.ts
index 1d859b991..727416a40 100644
--- a/client/src/app/shared/shared-forms/index.ts
+++ b/client/src/app/shared/shared-forms/index.ts
@@ -1,12 +1,14 @@
1export * from './form-validator.service' 1export * from './advanced-input-filter.component'
2export * from './form-reactive' 2export * from './form-reactive'
3export * from './select' 3export * from './form-validator.service'
4export * from './input-toggle-hidden.component' 4export * from './form-validator.service'
5export * from './input-switch.component' 5export * from './input-switch.component'
6export * from './input-toggle-hidden.component'
6export * from './markdown-textarea.component' 7export * from './markdown-textarea.component'
7export * from './peertube-checkbox.component' 8export * from './peertube-checkbox.component'
8export * from './preview-upload.component' 9export * from './preview-upload.component'
9export * from './reactive-file.component' 10export * from './reactive-file.component'
11export * from './select'
12export * from './shared-form.module'
10export * from './textarea-autoresize.directive' 13export * from './textarea-autoresize.directive'
11export * from './timestamp-input.component' 14export * from './timestamp-input.component'
12export * from './shared-form.module'
diff --git a/client/src/app/shared/shared-forms/shared-form.module.ts b/client/src/app/shared/shared-forms/shared-form.module.ts
index 9bdd138a1..5417f7342 100644
--- a/client/src/app/shared/shared-forms/shared-form.module.ts
+++ b/client/src/app/shared/shared-forms/shared-form.module.ts
@@ -5,6 +5,7 @@ import { FormsModule, ReactiveFormsModule } from '@angular/forms'
5import { NgSelectModule } from '@ng-select/ng-select' 5import { NgSelectModule } from '@ng-select/ng-select'
6import { SharedGlobalIconModule } from '../shared-icons' 6import { SharedGlobalIconModule } from '../shared-icons'
7import { SharedMainModule } from '../shared-main/shared-main.module' 7import { SharedMainModule } from '../shared-main/shared-main.module'
8import { AdvancedInputFilterComponent } from './advanced-input-filter.component'
8import { DynamicFormFieldComponent } from './dynamic-form-field.component' 9import { DynamicFormFieldComponent } from './dynamic-form-field.component'
9import { FormValidatorService } from './form-validator.service' 10import { FormValidatorService } from './form-validator.service'
10import { InputSwitchComponent } from './input-switch.component' 11import { InputSwitchComponent } from './input-switch.component'
@@ -52,7 +53,9 @@ import { TimestampInputComponent } from './timestamp-input.component'
52 SelectCheckboxComponent, 53 SelectCheckboxComponent,
53 SelectCustomValueComponent, 54 SelectCustomValueComponent,
54 55
55 DynamicFormFieldComponent 56 DynamicFormFieldComponent,
57
58 AdvancedInputFilterComponent
56 ], 59 ],
57 60
58 exports: [ 61 exports: [
@@ -78,7 +81,9 @@ import { TimestampInputComponent } from './timestamp-input.component'
78 SelectCheckboxComponent, 81 SelectCheckboxComponent,
79 SelectCustomValueComponent, 82 SelectCustomValueComponent,
80 83
81 DynamicFormFieldComponent 84 DynamicFormFieldComponent,
85
86 AdvancedInputFilterComponent
82 ], 87 ],
83 88
84 providers: [ 89 providers: [
diff --git a/client/src/app/shared/shared-main/video/video.service.ts b/client/src/app/shared/shared-main/video/video.service.ts
index 0b708b692..668e51f73 100644
--- a/client/src/app/shared/shared-main/video/video.service.ts
+++ b/client/src/app/shared/shared-main/video/video.service.ts
@@ -124,7 +124,23 @@ export class VideoService implements VideosProvider {
124 124
125 let params = new HttpParams() 125 let params = new HttpParams()
126 params = this.restService.addRestGetParams(params, pagination, sort) 126 params = this.restService.addRestGetParams(params, pagination, sort)
127 params = this.restService.addObjectParams(params, { search }) 127
128 if (search) {
129 const filters = this.restService.parseQueryStringFilter(search, {
130 isLive: {
131 prefix: 'isLive:',
132 isBoolean: true,
133 handler: v => {
134 if (v === 'true') return v
135 if (v === 'false') return v
136
137 return undefined
138 }
139 }
140 })
141
142 params = this.restService.addObjectParams(params, filters)
143 }
128 144
129 return this.authHttp 145 return this.authHttp
130 .get<ResultList<Video>>(UserService.BASE_USERS_URL + 'me/videos', { params }) 146 .get<ResultList<Video>>(UserService.BASE_USERS_URL + 'me/videos', { params })
diff --git a/client/src/app/shared/shared-search/advanced-search.model.ts b/client/src/app/shared/shared-search/advanced-search.model.ts
index 30badc8fa..0e3924841 100644
--- a/client/src/app/shared/shared-search/advanced-search.model.ts
+++ b/client/src/app/shared/shared-search/advanced-search.model.ts
@@ -1,4 +1,4 @@
1import { NSFWQuery, SearchTargetType } from '@shared/models' 1import { BooleanBothQuery, SearchTargetType } from '@shared/models'
2 2
3export class AdvancedSearch { 3export class AdvancedSearch {
4 startDate: string // ISO 8601 4 startDate: string // ISO 8601
@@ -7,7 +7,7 @@ export class AdvancedSearch {
7 originallyPublishedStartDate: string // ISO 8601 7 originallyPublishedStartDate: string // ISO 8601
8 originallyPublishedEndDate: string // ISO 8601 8 originallyPublishedEndDate: string // ISO 8601
9 9
10 nsfw: NSFWQuery 10 nsfw: BooleanBothQuery
11 11
12 categoryOneOf: string 12 categoryOneOf: string
13 13
@@ -33,7 +33,7 @@ export class AdvancedSearch {
33 endDate?: string 33 endDate?: string
34 originallyPublishedStartDate?: string 34 originallyPublishedStartDate?: string
35 originallyPublishedEndDate?: string 35 originallyPublishedEndDate?: string
36 nsfw?: NSFWQuery 36 nsfw?: BooleanBothQuery
37 categoryOneOf?: string 37 categoryOneOf?: string
38 licenceOneOf?: string 38 licenceOneOf?: string
39 languageOneOf?: string 39 languageOneOf?: string