diff options
Diffstat (limited to 'client/src/app')
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' | |||
6 | import { DomSanitizer } from '@angular/platform-browser' | 6 | import { DomSanitizer } from '@angular/platform-browser' |
7 | import { ActivatedRoute, Params, Router } from '@angular/router' | 7 | import { ActivatedRoute, Params, Router } from '@angular/router' |
8 | import { ConfirmService, MarkdownService, Notifier, RestPagination, RestTable, ServerService } from '@app/core' | 8 | import { ConfirmService, MarkdownService, Notifier, RestPagination, RestTable, ServerService } from '@app/core' |
9 | import { AdvancedInputFilter } from '@app/shared/shared-forms' | ||
9 | import { DropdownAction, Video, VideoService } from '@app/shared/shared-main' | 10 | import { DropdownAction, Video, VideoService } from '@app/shared/shared-main' |
10 | import { VideoBlockService } from '@app/shared/shared-moderation' | 11 | import { VideoBlockService } from '@app/shared/shared-moderation' |
11 | import { VideoBlacklist, VideoBlacklistType } from '@shared/models' | 12 | import { 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' | |||
2 | import { AfterViewInit, Component, OnInit } from '@angular/core' | 2 | import { AfterViewInit, Component, OnInit } from '@angular/core' |
3 | import { ActivatedRoute, Router } from '@angular/router' | 3 | import { ActivatedRoute, Router } from '@angular/router' |
4 | import { AuthService, ConfirmService, MarkdownService, Notifier, RestPagination, RestTable } from '@app/core' | 4 | import { AuthService, ConfirmService, MarkdownService, Notifier, RestPagination, RestTable } from '@app/core' |
5 | import { AdvancedInputFilter } from '@app/shared/shared-forms' | ||
5 | import { DropdownAction } from '@app/shared/shared-main' | 6 | import { DropdownAction } from '@app/shared/shared-main' |
6 | import { BulkService } from '@app/shared/shared-moderation' | 7 | import { BulkService } from '@app/shared/shared-moderation' |
7 | import { VideoCommentAdmin, VideoCommentService } from '@app/shared/shared-video-comment' | 8 | import { 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 | |||
39 | p-tableCheckbox { | 32 | p-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 @@ | |||
1 | import { SortMeta } from 'primeng/api' | 1 | import { SortMeta } from 'primeng/api' |
2 | import { Component, OnInit, ViewChild } from '@angular/core' | 2 | import { AfterViewInit, Component, OnInit, ViewChild } from '@angular/core' |
3 | import { ActivatedRoute, Params, Router } from '@angular/router' | 3 | import { ActivatedRoute, Router } from '@angular/router' |
4 | import { AuthService, ConfirmService, Notifier, RestPagination, RestTable, ServerService, UserService } from '@app/core' | 4 | import { AuthService, ConfirmService, Notifier, RestPagination, RestTable, ServerService, UserService } from '@app/core' |
5 | import { Account, DropdownAction } from '@app/shared/shared-main' | 5 | import { AdvancedInputFilter } from '@app/shared/shared-forms' |
6 | import { DropdownAction } from '@app/shared/shared-main' | ||
6 | import { UserBanModalComponent } from '@app/shared/shared-moderation' | 7 | import { UserBanModalComponent } from '@app/shared/shared-moderation' |
7 | import { ServerConfig, User, UserRole } from '@shared/models' | 8 | import { 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 | }) |
21 | export class UserListComponent extends RestTable implements OnInit { | 22 | export 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 @@ | |||
1 | import { concat, Observable, Subject } from 'rxjs' | 1 | import { concat, Observable } from 'rxjs' |
2 | import { debounceTime, tap, toArray } from 'rxjs/operators' | 2 | import { tap, toArray } from 'rxjs/operators' |
3 | import { Component, OnInit, ViewChild } from '@angular/core' | 3 | import { AfterViewInit, Component, OnInit, ViewChild } from '@angular/core' |
4 | import { ActivatedRoute, Router } from '@angular/router' | 4 | import { ActivatedRoute, Router } from '@angular/router' |
5 | import { AuthService, ComponentPagination, ConfirmService, Notifier, ScreenService, ServerService, User } from '@app/core' | 5 | import { AuthService, ComponentPagination, ConfirmService, Notifier, RouteFilter, ScreenService, ServerService, User } from '@app/core' |
6 | import { DisableForReuseHook } from '@app/core/routing/disable-for-reuse-hook' | 6 | import { DisableForReuseHook } from '@app/core/routing/disable-for-reuse-hook' |
7 | import { immutableAssign } from '@app/helpers' | 7 | import { immutableAssign } from '@app/helpers' |
8 | import { AdvancedInputFilter } from '@app/shared/shared-forms' | ||
8 | import { DropdownAction, Video, VideoService } from '@app/shared/shared-main' | 9 | import { DropdownAction, Video, VideoService } from '@app/shared/shared-main' |
9 | import { LiveStreamInformationComponent } from '@app/shared/shared-video-live' | 10 | import { LiveStreamInformationComponent } from '@app/shared/shared-video-live' |
10 | import { MiniatureDisplayOptions, SelectionType, VideosSelectionComponent } from '@app/shared/shared-video-miniature' | 11 | import { 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 | }) |
18 | export class MyVideosComponent implements OnInit, DisableForReuseHook { | 19 | export 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 @@ | |||
1 | import * as debug from 'debug' | 1 | import * as debug from 'debug' |
2 | import { LazyLoadEvent, SortMeta } from 'primeng/api' | 2 | import { LazyLoadEvent, SortMeta } from 'primeng/api' |
3 | import { Subject } from 'rxjs' | 3 | import { Subject } from 'rxjs' |
4 | import { debounceTime, distinctUntilChanged } from 'rxjs/operators' | 4 | import { ActivatedRoute, Router } from '@angular/router' |
5 | import { ActivatedRoute, Params, Router } from '@angular/router' | ||
6 | import { peertubeLocalStorage } from '@root-helpers/peertube-web-storage' | 5 | import { peertubeLocalStorage } from '@root-helpers/peertube-web-storage' |
6 | import { RouteFilter } from '../routing' | ||
7 | import { RestPagination } from './rest-pagination' | 7 | import { RestPagination } from './rest-pagination' |
8 | 8 | ||
9 | const logger = debug('peertube:tables:RestTable') | 9 | const logger = debug('peertube:tables:RestTable') |
10 | 10 | ||
11 | export abstract class RestTable { | 11 | export 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' | |||
5 | export * from './menu-guard.service' | 5 | export * from './menu-guard.service' |
6 | export * from './preload-selected-modules-list' | 6 | export * from './preload-selected-modules-list' |
7 | export * from './redirect.service' | 7 | export * from './redirect.service' |
8 | export * from './route-filter' | ||
8 | export * from './server-config-resolver.service' | 9 | export * from './server-config-resolver.service' |
9 | export * from './unlogged-guard.service' | 10 | export * from './unlogged-guard.service' |
10 | export * from './user-right-guard.service' | 11 | export * 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 @@ | |||
1 | import * as debug from 'debug' | ||
2 | import { Subject } from 'rxjs' | ||
3 | import { debounceTime, distinctUntilChanged } from 'rxjs/operators' | ||
4 | import { ActivatedRoute, Params, Router } from '@angular/router' | ||
5 | |||
6 | const logger = debug('peertube:tables:RouteFilter') | ||
7 | |||
8 | export 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:"' + abuse.reporterAccount.displayName + '"' }" | 10 | <a [routerLink]="[ '.' ]" [queryParams]="{ 'search': 'reporter:"' + abuse.reporterAccount.displayName + '"' }" |
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:"' + abuse.reporterAccount.displayName + '"' }" | 19 | <a [routerLink]="[ '.' ]" [queryParams]="{ 'search': 'reporter:"' + abuse.reporterAccount.displayName + '"' }" |
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:"' +abuse.flaggedAccount.displayName + '"' }" | 30 | <a [routerLink]="[ '.' ]" [queryParams]="{ 'search': 'reportee:"' +abuse.flaggedAccount.displayName + '"' }" |
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:"' +abuse.flaggedAccount.displayName + '"' }" | 39 | <a *ngIf="isAdminView" [routerLink]="[ '.' ]" [queryParams]="{ 'search': 'reportee:"' +abuse.flaggedAccount.displayName + '"' }" |
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 @@ | |||
1 | import { Component, Input } from '@angular/core' | 1 | import { Component, Input } from '@angular/core' |
2 | import { durationToString } from '@app/helpers' | 2 | import { durationToString } from '@app/helpers' |
3 | import { Account } from '@app/shared/shared-main' | ||
4 | import { AbusePredefinedReasonsString } from '@shared/models' | 3 | import { AbusePredefinedReasonsString } from '@shared/models' |
5 | import { ProcessedAbuse } from './processed-abuse.model' | 4 | import { ProcessedAbuse } from './processed-abuse.model' |
6 | 5 | ||
@@ -12,7 +11,6 @@ import { ProcessedAbuse } from './processed-abuse.model' | |||
12 | export class AbuseDetailsComponent { | 11 | export 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' | |||
14 | import { AbuseMessageModalComponent } from './abuse-message-modal.component' | 14 | import { AbuseMessageModalComponent } from './abuse-message-modal.component' |
15 | import { ModerationCommentModalComponent } from './moderation-comment-modal.component' | 15 | import { ModerationCommentModalComponent } from './moderation-comment-modal.component' |
16 | import { ProcessedAbuse } from './processed-abuse.model' | 16 | import { ProcessedAbuse } from './processed-abuse.model' |
17 | import { AdvancedInputFilter } from '../shared-forms' | ||
17 | 18 | ||
18 | const logger = debug('peertube:moderation:AbuseListTableComponent') | 19 | const logger = debug('peertube:moderation:AbuseListTableComponent') |
19 | 20 | ||
@@ -24,7 +25,6 @@ const logger = debug('peertube:moderation:AbuseListTableComponent') | |||
24 | }) | 25 | }) |
25 | export class AbuseListTableComponent extends RestTable implements OnInit, AfterViewInit { | 26 | export 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 | |||
4 | input { | ||
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 @@ | |||
1 | import { Component, EventEmitter, Input, Output } from '@angular/core' | ||
2 | import { Params } from '@angular/router' | ||
3 | |||
4 | export 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 | }) | ||
14 | export 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 @@ | |||
1 | export * from './form-validator.service' | 1 | export * from './advanced-input-filter.component' |
2 | export * from './form-reactive' | 2 | export * from './form-reactive' |
3 | export * from './select' | 3 | export * from './form-validator.service' |
4 | export * from './input-toggle-hidden.component' | 4 | export * from './form-validator.service' |
5 | export * from './input-switch.component' | 5 | export * from './input-switch.component' |
6 | export * from './input-toggle-hidden.component' | ||
6 | export * from './markdown-textarea.component' | 7 | export * from './markdown-textarea.component' |
7 | export * from './peertube-checkbox.component' | 8 | export * from './peertube-checkbox.component' |
8 | export * from './preview-upload.component' | 9 | export * from './preview-upload.component' |
9 | export * from './reactive-file.component' | 10 | export * from './reactive-file.component' |
11 | export * from './select' | ||
12 | export * from './shared-form.module' | ||
10 | export * from './textarea-autoresize.directive' | 13 | export * from './textarea-autoresize.directive' |
11 | export * from './timestamp-input.component' | 14 | export * from './timestamp-input.component' |
12 | export * 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' | |||
5 | import { NgSelectModule } from '@ng-select/ng-select' | 5 | import { NgSelectModule } from '@ng-select/ng-select' |
6 | import { SharedGlobalIconModule } from '../shared-icons' | 6 | import { SharedGlobalIconModule } from '../shared-icons' |
7 | import { SharedMainModule } from '../shared-main/shared-main.module' | 7 | import { SharedMainModule } from '../shared-main/shared-main.module' |
8 | import { AdvancedInputFilterComponent } from './advanced-input-filter.component' | ||
8 | import { DynamicFormFieldComponent } from './dynamic-form-field.component' | 9 | import { DynamicFormFieldComponent } from './dynamic-form-field.component' |
9 | import { FormValidatorService } from './form-validator.service' | 10 | import { FormValidatorService } from './form-validator.service' |
10 | import { InputSwitchComponent } from './input-switch.component' | 11 | import { 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 @@ | |||
1 | import { NSFWQuery, SearchTargetType } from '@shared/models' | 1 | import { BooleanBothQuery, SearchTargetType } from '@shared/models' |
2 | 2 | ||
3 | export class AdvancedSearch { | 3 | export 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 |