diff options
author | Rigel Kent <sendmemail@rigelk.eu> | 2020-05-03 23:01:57 +0200 |
---|---|---|
committer | Rigel Kent <par@rigelk.eu> | 2020-05-04 15:01:44 +0200 |
commit | 25a42e293be90d35afad2096e9db2fa3d617d855 (patch) | |
tree | 5739da8e2aed75447cf0fd608089cc8849c0da33 | |
parent | 801d957155d574bda984206021cdd1fe58ef56b9 (diff) | |
download | PeerTube-25a42e293be90d35afad2096e9db2fa3d617d855.tar.gz PeerTube-25a42e293be90d35afad2096e9db2fa3d617d855.tar.zst PeerTube-25a42e293be90d35afad2096e9db2fa3d617d855.zip |
Fix rowsPerPage change, add filter clear button, update video-abuse-list search query param dynamically
22 files changed, 175 insertions, 64 deletions
diff --git a/client/src/app/+admin/follows/followers-list/followers-list.component.html b/client/src/app/+admin/follows/followers-list/followers-list.component.html index a3be5961b..7b75bd453 100644 --- a/client/src/app/+admin/follows/followers-list/followers-list.component.html +++ b/client/src/app/+admin/follows/followers-list/followers-list.component.html | |||
@@ -6,10 +6,14 @@ | |||
6 | > | 6 | > |
7 | <ng-template pTemplate="caption"> | 7 | <ng-template pTemplate="caption"> |
8 | <div class="caption"> | 8 | <div class="caption"> |
9 | <input | 9 | <div class="ml-auto has-feedback has-clear"> |
10 | type="text" name="table-filter" id="table-filter" i18n-placeholder placeholder="Filter..." | 10 | <input |
11 | (keyup)="onSearch($event)" | 11 | type="text" name="table-filter" id="table-filter" i18n-placeholder placeholder="Filter..." |
12 | > | 12 | (keyup)="onSearch($event)" |
13 | > | ||
14 | <a class="glyphicon glyphicon-remove-sign form-control-feedback form-control-clear" (click)="resetSearch()"></a> | ||
15 | <span class="sr-only" i18n>Clear filters</span> | ||
16 | </div> | ||
13 | </div> | 17 | </div> |
14 | </ng-template> | 18 | </ng-template> |
15 | 19 | ||
diff --git a/client/src/app/+admin/follows/following-list/following-list.component.html b/client/src/app/+admin/follows/following-list/following-list.component.html index 4c232e29d..5769c7b53 100644 --- a/client/src/app/+admin/follows/following-list/following-list.component.html +++ b/client/src/app/+admin/follows/following-list/following-list.component.html | |||
@@ -6,11 +6,13 @@ | |||
6 | > | 6 | > |
7 | <ng-template pTemplate="caption"> | 7 | <ng-template pTemplate="caption"> |
8 | <div class="caption"> | 8 | <div class="caption"> |
9 | <div class="ml-auto"> | 9 | <div class="ml-auto has-feedback has-clear"> |
10 | <input | 10 | <input |
11 | type="text" name="table-filter" id="table-filter" i18n-placeholder placeholder="Filter..." | 11 | type="text" name="table-filter" id="table-filter" i18n-placeholder placeholder="Filter..." |
12 | (keyup)="onSearch($event)" | 12 | (keyup)="onSearch($event)" |
13 | > | 13 | > |
14 | <a class="glyphicon glyphicon-remove-sign form-control-feedback form-control-clear" (click)="resetSearch()"></a> | ||
15 | <span class="sr-only" i18n>Clear filters</span> | ||
14 | </div> | 16 | </div> |
15 | <a class="ml-2 follow-button" (click)="addDomainsToFollow()" (key.enter)="addDomainsToFollow()"> | 17 | <a class="ml-2 follow-button" (click)="addDomainsToFollow()" (key.enter)="addDomainsToFollow()"> |
16 | <my-global-icon iconName="add"></my-global-icon> | 18 | <my-global-icon iconName="add"></my-global-icon> |
diff --git a/client/src/app/+admin/follows/video-redundancies-list/video-redundancies-list.component.html b/client/src/app/+admin/follows/video-redundancies-list/video-redundancies-list.component.html index 99d8719a3..592287ea0 100644 --- a/client/src/app/+admin/follows/video-redundancies-list/video-redundancies-list.component.html +++ b/client/src/app/+admin/follows/video-redundancies-list/video-redundancies-list.component.html | |||
@@ -14,7 +14,7 @@ | |||
14 | <p-table | 14 | <p-table |
15 | [value]="videoRedundancies" [lazy]="true" [paginator]="totalRecords > 0" [totalRecords]="totalRecords" [rows]="rowsPerPage" | 15 | [value]="videoRedundancies" [lazy]="true" [paginator]="totalRecords > 0" [totalRecords]="totalRecords" [rows]="rowsPerPage" |
16 | [sortField]="sort.field" [sortOrder]="sort.order" (onLazyLoad)="loadLazy($event)" dataKey="id" | 16 | [sortField]="sort.field" [sortOrder]="sort.order" (onLazyLoad)="loadLazy($event)" dataKey="id" |
17 | (onPage)="onPage()" [expandedRowKeys]="expandedRows" | 17 | (onPage)="onPage($event)" [expandedRowKeys]="expandedRows" |
18 | > | 18 | > |
19 | <ng-template pTemplate="header"> | 19 | <ng-template pTemplate="header"> |
20 | <tr> | 20 | <tr> |
diff --git a/client/src/app/+admin/moderation/instance-blocklist/instance-account-blocklist.component.html b/client/src/app/+admin/moderation/instance-blocklist/instance-account-blocklist.component.html index 99b4e267c..262705603 100644 --- a/client/src/app/+admin/moderation/instance-blocklist/instance-account-blocklist.component.html +++ b/client/src/app/+admin/moderation/instance-blocklist/instance-account-blocklist.component.html | |||
@@ -6,11 +6,13 @@ | |||
6 | > | 6 | > |
7 | <ng-template pTemplate="caption"> | 7 | <ng-template pTemplate="caption"> |
8 | <div class="caption"> | 8 | <div class="caption"> |
9 | <div class="ml-auto"> | 9 | <div class="ml-auto has-feedback has-clear"> |
10 | <input | 10 | <input |
11 | type="text" name="table-filter" id="table-filter" i18n-placeholder placeholder="Filter..." | 11 | type="text" name="table-filter" id="table-filter" i18n-placeholder placeholder="Filter..." |
12 | (keyup)="onSearch($event)" | 12 | (keyup)="onSearch($event)" |
13 | > | 13 | > |
14 | <a class="glyphicon glyphicon-remove-sign form-control-feedback form-control-clear" (click)="resetSearch()"></a> | ||
15 | <span class="sr-only" i18n>Clear filters</span> | ||
14 | </div> | 16 | </div> |
15 | </div> | 17 | </div> |
16 | </ng-template> | 18 | </ng-template> |
diff --git a/client/src/app/+admin/moderation/instance-blocklist/instance-server-blocklist.component.html b/client/src/app/+admin/moderation/instance-blocklist/instance-server-blocklist.component.html index aecdca387..17364ae04 100644 --- a/client/src/app/+admin/moderation/instance-blocklist/instance-server-blocklist.component.html +++ b/client/src/app/+admin/moderation/instance-blocklist/instance-server-blocklist.component.html | |||
@@ -6,11 +6,13 @@ | |||
6 | > | 6 | > |
7 | <ng-template pTemplate="caption"> | 7 | <ng-template pTemplate="caption"> |
8 | <div class="caption"> | 8 | <div class="caption"> |
9 | <div class="ml-auto"> | 9 | <div class="ml-auto has-feedback has-clear"> |
10 | <input | 10 | <input |
11 | type="text" name="table-filter" id="table-filter" i18n-placeholder placeholder="Filter..." | 11 | type="text" name="table-filter" id="table-filter" i18n-placeholder placeholder="Filter..." |
12 | (keyup)="onSearch($event)" | 12 | (keyup)="onSearch($event)" |
13 | > | 13 | > |
14 | <a class="glyphicon glyphicon-remove-sign form-control-feedback form-control-clear" (click)="resetSearch()"></a> | ||
15 | <span class="sr-only" i18n>Clear filters</span> | ||
14 | </div> | 16 | </div> |
15 | <a class="ml-2 block-button" (click)="addServersToBlock()" (key.enter)="addServersToBlock()"> | 17 | <a class="ml-2 block-button" (click)="addServersToBlock()" (key.enter)="addServersToBlock()"> |
16 | <my-global-icon iconName="add"></my-global-icon> | 18 | <my-global-icon iconName="add"></my-global-icon> |
diff --git a/client/src/app/+admin/moderation/video-abuse-list/video-abuse-details.component.html b/client/src/app/+admin/moderation/video-abuse-list/video-abuse-details.component.html index 704d43ac4..588d38395 100644 --- a/client/src/app/+admin/moderation/video-abuse-list/video-abuse-details.component.html +++ b/client/src/app/+admin/moderation/video-abuse-list/video-abuse-details.component.html | |||
@@ -14,7 +14,7 @@ | |||
14 | alt="Avatar" | 14 | alt="Avatar" |
15 | > | 15 | > |
16 | <div> | 16 | <div> |
17 | <span class="text-muted">{{ createByString(videoAbuse.reporterAccount) }}</span> | 17 | <span class="text-muted">{{ videoAbuse.reporterAccount.nameWithHost }}</span> |
18 | </div> | 18 | </div> |
19 | </a> | 19 | </a> |
20 | <a [routerLink]="[ '/admin/moderation/video-abuses/list' ]" [queryParams]="{ 'search': 'reportee:"' + videoAbuse.reporterAccount.displayName + '"' }" class="ml-auto text-muted video-details-links" i18n> | 20 | <a [routerLink]="[ '/admin/moderation/video-abuses/list' ]" [queryParams]="{ 'search': 'reportee:"' + videoAbuse.reporterAccount.displayName + '"' }" class="ml-auto text-muted video-details-links" i18n> |
@@ -34,7 +34,7 @@ | |||
34 | alt="Avatar" | 34 | alt="Avatar" |
35 | > | 35 | > |
36 | <div> | 36 | <div> |
37 | <span class="text-muted">{{ videoAbuse.video.channel.ownerAccount ? createByString(videoAbuse.video.channel.ownerAccount) : '' }}</span> | 37 | <span class="text-muted">{{ videoAbuse.video.channel.ownerAccount ? videoAbuse.video.channel.ownerAccount.nameWithHost : '' }}</span> |
38 | </div> | 38 | </div> |
39 | </a> | 39 | </a> |
40 | <a [routerLink]="[ '/admin/moderation/video-abuses/list' ]" [queryParams]="{ 'search': 'reportee:"' +videoAbuse.video.channel.ownerAccount.displayName + '"' }" class="ml-auto text-muted video-details-links" i18n> | 40 | <a [routerLink]="[ '/admin/moderation/video-abuses/list' ]" [queryParams]="{ 'search': 'reportee:"' +videoAbuse.video.channel.ownerAccount.displayName + '"' }" class="ml-auto text-muted video-details-links" i18n> |
diff --git a/client/src/app/+admin/moderation/video-abuse-list/video-abuse-details.component.ts b/client/src/app/+admin/moderation/video-abuse-list/video-abuse-details.component.ts index 5481915b9..d9cb19845 100644 --- a/client/src/app/+admin/moderation/video-abuse-list/video-abuse-details.component.ts +++ b/client/src/app/+admin/moderation/video-abuse-list/video-abuse-details.component.ts | |||
@@ -1,6 +1,7 @@ | |||
1 | import { Component, ViewEncapsulation, Input } from '@angular/core' | 1 | import { Component, Input } from '@angular/core' |
2 | import { VideoAbuse } from '../../../../../../shared' | ||
3 | import { Account } from '@app/shared/account/account.model' | 2 | import { Account } from '@app/shared/account/account.model' |
3 | import { Actor } from '@app/shared/actor/actor.model' | ||
4 | import { ProcessedVideoAbuse } from './video-abuse-list.component' | ||
4 | 5 | ||
5 | @Component({ | 6 | @Component({ |
6 | selector: 'my-video-abuse-details', | 7 | selector: 'my-video-abuse-details', |
@@ -8,9 +9,9 @@ import { Account } from '@app/shared/account/account.model' | |||
8 | styleUrls: [ '../moderation.component.scss' ] | 9 | styleUrls: [ '../moderation.component.scss' ] |
9 | }) | 10 | }) |
10 | export class VideoAbuseDetailsComponent { | 11 | export class VideoAbuseDetailsComponent { |
11 | @Input() videoAbuse: VideoAbuse | 12 | @Input() videoAbuse: ProcessedVideoAbuse |
12 | 13 | ||
13 | createByString (account: Account) { | 14 | switchToDefaultAvatar ($event: Event) { |
14 | return Account.CREATE_BY_STRING(account.name, account.host) | 15 | ($event.target as HTMLImageElement).src = Actor.GET_DEFAULT_AVATAR_URL() |
15 | } | 16 | } |
16 | } | 17 | } |
diff --git a/client/src/app/+admin/moderation/video-abuse-list/video-abuse-list.component.html b/client/src/app/+admin/moderation/video-abuse-list/video-abuse-list.component.html index 2e7b60e2f..ba05073cf 100644 --- a/client/src/app/+admin/moderation/video-abuse-list/video-abuse-list.component.html +++ b/client/src/app/+admin/moderation/video-abuse-list/video-abuse-list.component.html | |||
@@ -3,25 +3,19 @@ | |||
3 | [sortField]="sort.field" [sortOrder]="sort.order" (onLazyLoad)="loadLazy($event)" dataKey="id" [resizableColumns]="true" | 3 | [sortField]="sort.field" [sortOrder]="sort.order" (onLazyLoad)="loadLazy($event)" dataKey="id" [resizableColumns]="true" |
4 | [showCurrentPageReport]="true" i18n-currentPageReportTemplate | 4 | [showCurrentPageReport]="true" i18n-currentPageReportTemplate |
5 | currentPageReportTemplate="Showing {{'{first}'}} to {{'{last}'}} of {{'{totalRecords}'}} reports" | 5 | currentPageReportTemplate="Showing {{'{first}'}} to {{'{last}'}} of {{'{totalRecords}'}} reports" |
6 | (onPage)="onPage()" [expandedRowKeys]="expandedRows" | 6 | (onPage)="onPage($event)" [expandedRowKeys]="expandedRows" |
7 | > | 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"> | 11 | <div class="input-group has-feedback has-clear"> |
12 | <div class="input-group-prepend c-hand" ngbDropdown placement="bottom-left auto" container="body"> | 12 | <div class="input-group-prepend c-hand" ngbDropdown placement="bottom-left auto" container="body"> |
13 | <div class="input-group-text" ngbDropdownToggle> | 13 | <div class="input-group-text" ngbDropdownToggle> |
14 | <span class="caret" aria-haspopup="menu" role="button"></span> | 14 | <span class="caret" aria-haspopup="menu" role="button"></span> |
15 | </div> | 15 | </div> |
16 | 16 | ||
17 | <div role="menu" ngbDropdownMenu> | 17 | <div role="menu" ngbDropdownMenu> |
18 | <h6 class="dropdown-header" i18n>Filter reports</h6> | 18 | <h6 class="dropdown-header" i18n>Advanced report filters</h6> |
19 | |||
20 | <!-- TODO: | ||
21 | <div class="dropdown-item" i18n>Reports opened by admins</div> | ||
22 | <div class="dropdown-item" i18n>Reports on videos with multiple reports</div> | ||
23 | <div class="dropdown-item" i18n>Unassigned reports</div> | ||
24 | --> | ||
25 | <a [routerLink]="[ '/admin/moderation/video-abuses/list' ]" [queryParams]="{ 'search': 'state:pending' }" class="dropdown-item" i18n>Unsolved reports</a> | 19 | <a [routerLink]="[ '/admin/moderation/video-abuses/list' ]" [queryParams]="{ 'search': 'state:pending' }" class="dropdown-item" i18n>Unsolved reports</a> |
26 | <a [routerLink]="[ '/admin/moderation/video-abuses/list' ]" [queryParams]="{ 'search': 'state:accepted' }" class="dropdown-item" i18n>Accepted reports</a> | 20 | <a [routerLink]="[ '/admin/moderation/video-abuses/list' ]" [queryParams]="{ 'search': 'state:accepted' }" class="dropdown-item" i18n>Accepted reports</a> |
27 | <a [routerLink]="[ '/admin/moderation/video-abuses/list' ]" [queryParams]="{ 'search': 'state:rejected' }" class="dropdown-item" i18n>Refused reports</a> | 21 | <a [routerLink]="[ '/admin/moderation/video-abuses/list' ]" [queryParams]="{ 'search': 'state:rejected' }" class="dropdown-item" i18n>Refused reports</a> |
@@ -31,8 +25,10 @@ | |||
31 | </div> | 25 | </div> |
32 | <input | 26 | <input |
33 | type="text" name="table-filter" id="table-filter" i18n-placeholder placeholder="Filter..." | 27 | type="text" name="table-filter" id="table-filter" i18n-placeholder placeholder="Filter..." |
34 | (keyup)="onSearch($event)" | 28 | (keyup)="onAbuseSearch($event)" |
35 | > | 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> | ||
36 | </div> | 32 | </div> |
37 | </div> | 33 | </div> |
38 | </div> | 34 | </div> |
@@ -68,7 +64,7 @@ | |||
68 | > | 64 | > |
69 | <div> | 65 | <div> |
70 | {{ videoAbuse.reporterAccount.displayName }} | 66 | {{ videoAbuse.reporterAccount.displayName }} |
71 | <span class="text-muted">{{ createByString(videoAbuse.reporterAccount) }}</span> | 67 | <span class="text-muted">{{ videoAbuse.reporterAccount.nameWithHost }}</span> |
72 | </div> | 68 | </div> |
73 | </div> | 69 | </div> |
74 | </a> | 70 | </a> |
diff --git a/client/src/app/+admin/moderation/video-abuse-list/video-abuse-list.component.ts b/client/src/app/+admin/moderation/video-abuse-list/video-abuse-list.component.ts index 83d194d52..f54e3dccd 100644 --- a/client/src/app/+admin/moderation/video-abuse-list/video-abuse-list.component.ts +++ b/client/src/app/+admin/moderation/video-abuse-list/video-abuse-list.component.ts | |||
@@ -16,9 +16,23 @@ import { getAbsoluteAPIUrl } from '@app/shared/misc/utils' | |||
16 | import { DomSanitizer } from '@angular/platform-browser' | 16 | import { DomSanitizer } from '@angular/platform-browser' |
17 | import { BlocklistService } from '@app/shared/blocklist' | 17 | import { BlocklistService } from '@app/shared/blocklist' |
18 | import { VideoService } from '@app/shared/video/video.service' | 18 | import { VideoService } from '@app/shared/video/video.service' |
19 | import { ActivatedRoute } from '@angular/router' | 19 | import { ActivatedRoute, Params, Router } from '@angular/router' |
20 | import { filter } from 'rxjs/operators' | 20 | import { filter } from 'rxjs/operators' |
21 | 21 | ||
22 | export type ProcessedVideoAbuse = VideoAbuse & { | ||
23 | moderationCommentHtml?: string, | ||
24 | reasonHtml?: string | ||
25 | embedHtml?: string | ||
26 | updatedAt?: Date | ||
27 | // override bare server-side definitions with rich client-side definitions | ||
28 | reporterAccount: Account | ||
29 | video: VideoAbuse['video'] & { | ||
30 | channel: VideoAbuse['video']['channel'] & { | ||
31 | ownerAccount: Account | ||
32 | } | ||
33 | } | ||
34 | } | ||
35 | |||
22 | @Component({ | 36 | @Component({ |
23 | selector: 'my-video-abuse-list', | 37 | selector: 'my-video-abuse-list', |
24 | templateUrl: './video-abuse-list.component.html', | 38 | templateUrl: './video-abuse-list.component.html', |
@@ -27,7 +41,7 @@ import { filter } from 'rxjs/operators' | |||
27 | export class VideoAbuseListComponent extends RestTable implements OnInit, AfterViewInit { | 41 | export class VideoAbuseListComponent extends RestTable implements OnInit, AfterViewInit { |
28 | @ViewChild('moderationCommentModal', { static: true }) moderationCommentModal: ModerationCommentModalComponent | 42 | @ViewChild('moderationCommentModal', { static: true }) moderationCommentModal: ModerationCommentModalComponent |
29 | 43 | ||
30 | videoAbuses: (VideoAbuse & { moderationCommentHtml?: string, reasonHtml?: string })[] = [] | 44 | videoAbuses: ProcessedVideoAbuse[] = [] |
31 | totalRecords = 0 | 45 | totalRecords = 0 |
32 | sort: SortMeta = { field: 'createdAt', order: 1 } | 46 | sort: SortMeta = { field: 'createdAt', order: 1 } |
33 | pagination: RestPagination = { count: this.rowsPerPage, start: 0 } | 47 | pagination: RestPagination = { count: this.rowsPerPage, start: 0 } |
@@ -44,7 +58,8 @@ export class VideoAbuseListComponent extends RestTable implements OnInit, AfterV | |||
44 | private i18n: I18n, | 58 | private i18n: I18n, |
45 | private markdownRenderer: MarkdownService, | 59 | private markdownRenderer: MarkdownService, |
46 | private sanitizer: DomSanitizer, | 60 | private sanitizer: DomSanitizer, |
47 | private route: ActivatedRoute | 61 | private route: ActivatedRoute, |
62 | private router: Router | ||
48 | ) { | 63 | ) { |
49 | super() | 64 | super() |
50 | 65 | ||
@@ -212,15 +227,24 @@ export class VideoAbuseListComponent extends RestTable implements OnInit, AfterV | |||
212 | this.loadData() | 227 | this.loadData() |
213 | } | 228 | } |
214 | 229 | ||
215 | createByString (account: Account) { | 230 | /* Table filter functions */ |
216 | return Account.CREATE_BY_STRING(account.name, account.host) | 231 | onAbuseSearch (event: Event) { |
232 | this.onSearch(event) | ||
233 | this.setQueryParams((event.target as HTMLInputElement).value) | ||
234 | } | ||
235 | |||
236 | setQueryParams (search: string) { | ||
237 | const queryParams: Params = {} | ||
238 | if (search) Object.assign(queryParams, { search }) | ||
239 | this.router.navigate([ '/admin/moderation/video-abuses/list' ], { queryParams }) | ||
217 | } | 240 | } |
218 | 241 | ||
219 | setTableFilter (filter: string) { | 242 | resetTableFilter () { |
220 | // FIXME: cannot use ViewChild, so create a component for the filter input | 243 | this.setTableFilter('') |
221 | const filterInput = document.getElementById('table-filter') as HTMLInputElement | 244 | this.setQueryParams('') |
222 | if (filterInput) filterInput.value = filter | 245 | this.resetSearch() |
223 | } | 246 | } |
247 | /* END Table filter functions */ | ||
224 | 248 | ||
225 | isVideoAbuseAccepted (videoAbuse: VideoAbuse) { | 249 | isVideoAbuseAccepted (videoAbuse: VideoAbuse) { |
226 | return videoAbuse.state.id === VideoAbuseState.ACCEPTED | 250 | return videoAbuse.state.id === VideoAbuseState.ACCEPTED |
@@ -279,17 +303,20 @@ export class VideoAbuseListComponent extends RestTable implements OnInit, AfterV | |||
279 | }).subscribe( | 303 | }).subscribe( |
280 | async resultList => { | 304 | async resultList => { |
281 | this.totalRecords = resultList.total | 305 | this.totalRecords = resultList.total |
306 | this.videoAbuses = [] | ||
282 | 307 | ||
283 | this.videoAbuses = resultList.data | 308 | for (const abuse of resultList.data) { |
284 | |||
285 | for (const abuse of this.videoAbuses) { | ||
286 | Object.assign(abuse, { | 309 | Object.assign(abuse, { |
287 | reasonHtml: await this.toHtml(abuse.reason), | 310 | reasonHtml: await this.toHtml(abuse.reason), |
288 | moderationCommentHtml: await this.toHtml(abuse.moderationComment), | 311 | moderationCommentHtml: await this.toHtml(abuse.moderationComment), |
289 | embedHtml: this.sanitizer.bypassSecurityTrustHtml(this.getVideoEmbed(abuse)), | 312 | embedHtml: this.sanitizer.bypassSecurityTrustHtml(this.getVideoEmbed(abuse)), |
290 | reporterAccount: new Account(abuse.reporterAccount) | 313 | reporterAccount: new Account(abuse.reporterAccount) |
291 | }) | 314 | }) |
315 | |||
316 | if (abuse.video.channel?.ownerAccount) abuse.video.channel.ownerAccount = new Account(abuse.video.channel.ownerAccount) | ||
292 | if (abuse.updatedAt === abuse.createdAt) delete abuse.updatedAt | 317 | if (abuse.updatedAt === abuse.createdAt) delete abuse.updatedAt |
318 | |||
319 | this.videoAbuses.push(abuse as ProcessedVideoAbuse) | ||
293 | } | 320 | } |
294 | 321 | ||
295 | }, | 322 | }, |
diff --git a/client/src/app/+admin/moderation/video-blacklist-list/video-blacklist-list.component.html b/client/src/app/+admin/moderation/video-blacklist-list/video-blacklist-list.component.html index b3f7789df..eb194b023 100644 --- a/client/src/app/+admin/moderation/video-blacklist-list/video-blacklist-list.component.html +++ b/client/src/app/+admin/moderation/video-blacklist-list/video-blacklist-list.component.html | |||
@@ -3,15 +3,17 @@ | |||
3 | [sortField]="sort.field" [sortOrder]="sort.order" (onLazyLoad)="loadLazy($event)" dataKey="id" | 3 | [sortField]="sort.field" [sortOrder]="sort.order" (onLazyLoad)="loadLazy($event)" dataKey="id" |
4 | [showCurrentPageReport]="true" i18n-currentPageReportTemplate | 4 | [showCurrentPageReport]="true" i18n-currentPageReportTemplate |
5 | currentPageReportTemplate="Showing {{'{first}'}} to {{'{last}'}} of {{'{totalRecords}'}} blacklisted videos" | 5 | currentPageReportTemplate="Showing {{'{first}'}} to {{'{last}'}} of {{'{totalRecords}'}} blacklisted videos" |
6 | (onPage)="onPage()" [expandedRowKeys]="expandedRows" | 6 | (onPage)="onPage($event)" [expandedRowKeys]="expandedRows" |
7 | > | 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 has-feedback has-clear"> |
11 | <input | 11 | <input |
12 | type="text" name="table-filter" id="table-filter" i18n-placeholder placeholder="Filter..." | 12 | type="text" name="table-filter" id="table-filter" i18n-placeholder placeholder="Filter..." |
13 | (keyup)="onSearch($event)" | 13 | (keyup)="onSearch($event)" |
14 | > | 14 | > |
15 | <a class="glyphicon glyphicon-remove-sign form-control-feedback form-control-clear" (click)="resetSearch()"></a> | ||
16 | <span class="sr-only" i18n>Clear filters</span> | ||
15 | </div> | 17 | </div> |
16 | </div> | 18 | </div> |
17 | </ng-template> | 19 | </ng-template> |
diff --git a/client/src/app/+admin/system/jobs/jobs.component.html b/client/src/app/+admin/system/jobs/jobs.component.html index 05d573163..038dfa522 100644 --- a/client/src/app/+admin/system/jobs/jobs.component.html +++ b/client/src/app/+admin/system/jobs/jobs.component.html | |||
@@ -21,7 +21,7 @@ | |||
21 | <p-table | 21 | <p-table |
22 | [value]="jobs" [lazy]="true" [paginator]="totalRecords > 0" [totalRecords]="totalRecords" [rows]="rowsPerPage" dataKey="uniqId" | 22 | [value]="jobs" [lazy]="true" [paginator]="totalRecords > 0" [totalRecords]="totalRecords" [rows]="rowsPerPage" dataKey="uniqId" |
23 | [sortField]="sort.field" [sortOrder]="sort.order" (onLazyLoad)="loadLazy($event)" [first]="pagination.start" | 23 | [sortField]="sort.field" [sortOrder]="sort.order" (onLazyLoad)="loadLazy($event)" [first]="pagination.start" |
24 | [tableStyle]="{'table-layout':'auto'}" (onPage)="onPage()" [expandedRowKeys]="expandedRows" | 24 | [tableStyle]="{'table-layout':'auto'}" (onPage)="onPage($event)" [expandedRowKeys]="expandedRows" |
25 | > | 25 | > |
26 | <ng-template pTemplate="header"> | 26 | <ng-template pTemplate="header"> |
27 | <tr> | 27 | <tr> |
diff --git a/client/src/app/+admin/users/user-edit/user-password.component.scss b/client/src/app/+admin/users/user-edit/user-password.component.scss index 5cd93f6af..217d585af 100644 --- a/client/src/app/+admin/users/user-edit/user-password.component.scss +++ b/client/src/app/+admin/users/user-edit/user-password.component.scss | |||
@@ -16,3 +16,7 @@ input[type=submit] { | |||
16 | 16 | ||
17 | margin-top: 10px; | 17 | margin-top: 10px; |
18 | } | 18 | } |
19 | |||
20 | .input-group-append { | ||
21 | height: 30px; | ||
22 | } | ||
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 94c59cb9a..8b71dae79 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 | |||
@@ -8,12 +8,12 @@ | |||
8 | </div> | 8 | </div> |
9 | 9 | ||
10 | <p-table | 10 | <p-table |
11 | [value]="users" [lazy]="true" [paginator]="totalRecords > 0" [totalRecords]="totalRecords" [rows]="rowsPerPage" | 11 | [value]="users" [lazy]="true" [paginator]="totalRecords > 0" [totalRecords]="totalRecords" [rows]="rowsPerPage" [rowsPerPageOptions]="rowsPerPageOptions" |
12 | [sortField]="sort.field" [sortOrder]="sort.order" (onLazyLoad)="loadLazy($event)" dataKey="id" | 12 | [sortField]="sort.field" [sortOrder]="sort.order" (onLazyLoad)="loadLazy($event)" dataKey="id" |
13 | [(selection)]="selectedUsers" | 13 | [(selection)]="selectedUsers" |
14 | [showCurrentPageReport]="true" i18n-currentPageReportTemplate | 14 | [showCurrentPageReport]="true" i18n-currentPageReportTemplate |
15 | currentPageReportTemplate="Showing {{'{first}'}} to {{'{last}'}} of {{'{totalRecords}'}} users" | 15 | currentPageReportTemplate="Showing {{'{first}'}} to {{'{last}'}} of {{'{totalRecords}'}} users" |
16 | (onPage)="onPage()" [expandedRowKeys]="expandedRows" | 16 | (onPage)="onPage($event)" [expandedRowKeys]="expandedRows" |
17 | > | 17 | > |
18 | <ng-template pTemplate="caption"> | 18 | <ng-template pTemplate="caption"> |
19 | <div class="caption"> | 19 | <div class="caption"> |
@@ -25,11 +25,13 @@ | |||
25 | </my-action-dropdown> | 25 | </my-action-dropdown> |
26 | </div> | 26 | </div> |
27 | 27 | ||
28 | <div> | 28 | <div class="has-feedback has-clear"> |
29 | <input | 29 | <input |
30 | type="text" name="table-filter" id="table-filter" i18n-placeholder placeholder="Filter..." | 30 | type="text" name="table-filter" id="table-filter" i18n-placeholder placeholder="Filter..." |
31 | (keyup)="onSearch($event)" | 31 | (keyup)="onSearch($event)" |
32 | > | 32 | > |
33 | <a class="glyphicon glyphicon-remove-sign form-control-feedback form-control-clear" (click)="resetSearch()"></a> | ||
34 | <span class="sr-only" i18n>Clear filters</span> | ||
33 | </div> | 35 | </div> |
34 | </div> | 36 | </div> |
35 | </ng-template> | 37 | </ng-template> |
diff --git a/client/src/app/+my-account/my-account-video-channels/my-account-video-channel-edit.component.scss b/client/src/app/+my-account/my-account-video-channels/my-account-video-channel-edit.component.scss index ba27ee7ff..8f8af655c 100644 --- a/client/src/app/+my-account/my-account-video-channels/my-account-video-channel-edit.component.scss +++ b/client/src/app/+my-account/my-account-video-channels/my-account-video-channel-edit.component.scss | |||
@@ -19,6 +19,10 @@ my-actor-avatar-info { | |||
19 | @include peertube-input-group(fit-content); | 19 | @include peertube-input-group(fit-content); |
20 | } | 20 | } |
21 | 21 | ||
22 | .input-group-append { | ||
23 | height: 30px; | ||
24 | } | ||
25 | |||
22 | input { | 26 | input { |
23 | &[type=text] { | 27 | &[type=text] { |
24 | @include peertube-input-text(340px); | 28 | @include peertube-input-text(340px); |
diff --git a/client/src/app/+my-account/my-account-video-imports/my-account-video-imports.component.html b/client/src/app/+my-account/my-account-video-imports/my-account-video-imports.component.html index 7d447cdb3..37c6ad6b4 100644 --- a/client/src/app/+my-account/my-account-video-imports/my-account-video-imports.component.html +++ b/client/src/app/+my-account/my-account-video-imports/my-account-video-imports.component.html | |||
@@ -1,7 +1,7 @@ | |||
1 | <p-table | 1 | <p-table |
2 | [value]="videoImports" [lazy]="true" [paginator]="totalRecords > 0" [totalRecords]="totalRecords" [rows]="rowsPerPage" | 2 | [value]="videoImports" [lazy]="true" [paginator]="totalRecords > 0" [totalRecords]="totalRecords" [rows]="rowsPerPage" |
3 | [sortField]="sort.field" [sortOrder]="sort.order" (onLazyLoad)="loadLazy($event)" dataKey="id" | 3 | [sortField]="sort.field" [sortOrder]="sort.order" (onLazyLoad)="loadLazy($event)" dataKey="id" |
4 | (onPage)="onPage()" [expandedRowKeys]="expandedRows" | 4 | (onPage)="onPage($event)" [expandedRowKeys]="expandedRows" |
5 | > | 5 | > |
6 | <ng-template pTemplate="header"> | 6 | <ng-template pTemplate="header"> |
7 | <tr> | 7 | <tr> |
diff --git a/client/src/app/+signup/+register/register.component.scss b/client/src/app/+signup/+register/register.component.scss index cc60ef524..e135b5cb4 100644 --- a/client/src/app/+signup/+register/register.component.scss +++ b/client/src/app/+signup/+register/register.component.scss | |||
@@ -58,6 +58,10 @@ | |||
58 | @include peertube-input-group(400px); | 58 | @include peertube-input-group(400px); |
59 | } | 59 | } |
60 | 60 | ||
61 | .input-group-append { | ||
62 | height: 30px; | ||
63 | } | ||
64 | |||
61 | input:not([type=submit]) { | 65 | input:not([type=submit]) { |
62 | @include peertube-input-text(400px); | 66 | @include peertube-input-text(400px); |
63 | 67 | ||
diff --git a/client/src/app/shared/rest/rest-table.ts b/client/src/app/shared/rest/rest-table.ts index 4dd0f5ff3..d4e6cf5f2 100644 --- a/client/src/app/shared/rest/rest-table.ts +++ b/client/src/app/shared/rest/rest-table.ts | |||
@@ -74,10 +74,29 @@ export abstract class RestTable { | |||
74 | this.searchStream.next(target.value) | 74 | this.searchStream.next(target.value) |
75 | } | 75 | } |
76 | 76 | ||
77 | onPage () { | 77 | onPage (event: { first: number, rows: number }) { |
78 | if (this.rowsPerPage !== event.rows) { | ||
79 | this.rowsPerPage = event.rows | ||
80 | this.pagination = { | ||
81 | start: event.first, | ||
82 | count: this.rowsPerPage | ||
83 | } | ||
84 | this.loadData() | ||
85 | } | ||
78 | this.expandedRows = {} | 86 | this.expandedRows = {} |
79 | } | 87 | } |
80 | 88 | ||
89 | setTableFilter (filter: string) { | ||
90 | // FIXME: cannot use ViewChild, so create a component for the filter input | ||
91 | const filterInput = document.getElementById('table-filter') as HTMLInputElement | ||
92 | if (filterInput) filterInput.value = filter | ||
93 | } | ||
94 | |||
95 | resetSearch () { | ||
96 | this.searchStream.next('') | ||
97 | this.setTableFilter('') | ||
98 | } | ||
99 | |||
81 | protected abstract loadData (): void | 100 | protected abstract loadData (): void |
82 | 101 | ||
83 | private getSortLocalStorageKey () { | 102 | private getSortLocalStorageKey () { |
diff --git a/client/src/sass/application.scss b/client/src/sass/application.scss index 62503fc02..bbecd8ba8 100644 --- a/client/src/sass/application.scss +++ b/client/src/sass/application.scss | |||
@@ -339,6 +339,11 @@ table { | |||
339 | .peertube-select-container { | 339 | .peertube-select-container { |
340 | width: 100% !important; | 340 | width: 100% !important; |
341 | } | 341 | } |
342 | |||
343 | .caption input[type=text] { | ||
344 | width: unset !important; | ||
345 | flex-grow: 1; | ||
346 | } | ||
342 | } | 347 | } |
343 | } | 348 | } |
344 | } | 349 | } |
diff --git a/client/src/sass/bootstrap.scss b/client/src/sass/bootstrap.scss index 50f1dafed..cb266cc68 100644 --- a/client/src/sass/bootstrap.scss +++ b/client/src/sass/bootstrap.scss | |||
@@ -27,7 +27,7 @@ $icon-font-path: '~@neos21/bootstrap3-glyphicons/assets/fonts/'; | |||
27 | } | 27 | } |
28 | 28 | ||
29 | /* rules for dropdowns excepts when in button group, to avoid impacting the dropdown-toggle */ | 29 | /* rules for dropdowns excepts when in button group, to avoid impacting the dropdown-toggle */ |
30 | .dropdown:not(.btn-group):not(.dropdown-root):not(.action-dropdown) { | 30 | .dropdown:not(.btn-group):not(.dropdown-root):not(.action-dropdown):not(.input-group-prepend) { |
31 | z-index: z(dropdown) !important; | 31 | z-index: z(dropdown) !important; |
32 | 32 | ||
33 | &.list-overflow-menu, | 33 | &.list-overflow-menu, |
@@ -270,10 +270,9 @@ ngb-tooltip-window { | |||
270 | & > .form-control { | 270 | & > .form-control { |
271 | flex: initial; | 271 | flex: initial; |
272 | } | 272 | } |
273 | 273 | input.form-control { | |
274 | .input-group-prepend, | 274 | width: unset !important; |
275 | .input-group-append { | 275 | flex-grow: 1; |
276 | height: 30px; | ||
277 | } | 276 | } |
278 | 277 | ||
279 | .input-group-prepend + input { | 278 | .input-group-prepend + input { |
@@ -281,3 +280,35 @@ ngb-tooltip-window { | |||
281 | border-bottom-left-radius: 0 !important; | 280 | border-bottom-left-radius: 0 !important; |
282 | } | 281 | } |
283 | } | 282 | } |
283 | |||
284 | .has-feedback.has-clear { | ||
285 | position: relative; | ||
286 | |||
287 | input { | ||
288 | padding-right: 1.5rem !important; | ||
289 | } | ||
290 | |||
291 | .form-control-clear { | ||
292 | color: rgba(0, 0, 0, 0.4); | ||
293 | /* | ||
294 | * Enable pointer events as they have been disabled since Bootstrap 3.3 | ||
295 | * See https://github.com/twbs/bootstrap/pull/14104 | ||
296 | */ | ||
297 | pointer-events: all; | ||
298 | display: flex; | ||
299 | justify-content: center; | ||
300 | align-items: center; | ||
301 | position: absolute; | ||
302 | right: .5rem; | ||
303 | height: 95%; | ||
304 | |||
305 | &:hover { | ||
306 | color: rgba(0, 0, 0, 0.7); | ||
307 | cursor: pointer; | ||
308 | } | ||
309 | } | ||
310 | |||
311 | input:placeholder-shown + .form-control-clear { | ||
312 | display: none; | ||
313 | } | ||
314 | } | ||
diff --git a/client/src/sass/primeng-custom.scss b/client/src/sass/primeng-custom.scss index eab2b2dfd..d48f2dfc4 100644 --- a/client/src/sass/primeng-custom.scss +++ b/client/src/sass/primeng-custom.scss | |||
@@ -30,7 +30,8 @@ p-table { | |||
30 | 30 | ||
31 | .caption { | 31 | .caption { |
32 | height: 40px; | 32 | height: 40px; |
33 | display: flex; | 33 | width: 100%; |
34 | display: inline-flex; | ||
34 | align-items: center; | 35 | align-items: center; |
35 | 36 | ||
36 | .input-group-text { | 37 | .input-group-text { |
diff --git a/server/models/utils.ts b/server/models/utils.ts index 3e3825b32..956562e70 100644 --- a/server/models/utils.ts +++ b/server/models/utils.ts | |||
@@ -223,9 +223,12 @@ interface QueryStringFilterPrefixes { | |||
223 | [key: string]: string | { prefix: string, handler: Function, multiple?: boolean } | 223 | [key: string]: string | { prefix: string, handler: Function, multiple?: boolean } |
224 | } | 224 | } |
225 | 225 | ||
226 | function parseQueryStringFilter (q: string, prefixes: QueryStringFilterPrefixes) { | 226 | function parseQueryStringFilter (q: string, prefixes: QueryStringFilterPrefixes): { |
227 | search: string | ||
228 | [key: string]: string | number | string[] | number[] | ||
229 | } { | ||
227 | const tokens = q // tokenize only if we have a querystring | 230 | const tokens = q // tokenize only if we have a querystring |
228 | ? [].concat.apply([], q.split('"').map((v, i) => i % 2 ? v : v.split(' '))).filter(Boolean) | 231 | ? [].concat.apply([], q.split('"').map((v, i) => i % 2 ? v : v.split(' '))).filter(Boolean) // split by space unless using double quotes |
229 | : [] | 232 | : [] |
230 | 233 | ||
231 | // TODO: when Typescript supports Object.fromEntries, replace with the Object method | 234 | // TODO: when Typescript supports Object.fromEntries, replace with the Object method |
@@ -252,16 +255,18 @@ function parseQueryStringFilter (q: string, prefixes: QueryStringFilterPrefixes) | |||
252 | } | 255 | } |
253 | })).join(' '), | 256 | })).join(' '), |
254 | // filters defined in prefixes are added under their own name | 257 | // filters defined in prefixes are added under their own name |
255 | ...objectMap(prefixes, v => { | 258 | ...objectMap(prefixes, p => { |
256 | if (typeof v === "string") { | 259 | if (typeof p === "string") { |
257 | return tokens.filter(e => e.startsWith(v)).map(e => e.slice(v.length)) | 260 | return tokens.filter(e => e.startsWith(p)).map(e => e.slice(p.length)) // we keep the matched item, and remove its prefix |
258 | } else { | 261 | } else { |
259 | const _tokens = tokens.filter(e => e.startsWith(v.prefix)).map(e => e.slice(v.prefix.length)).map(v.handler) | 262 | const _tokens = tokens.filter(e => e.startsWith(p.prefix)).map(e => e.slice(p.prefix.length)).map(p.handler) |
260 | return !v.multiple | 263 | // multiple is false by default, meaning we usually just keep the first occurence of a given prefix |
261 | ? _tokens.length > 0 | 264 | if (!p.multiple && _tokens.length > 0) { |
262 | ? _tokens[0] | 265 | return _tokens[0] |
263 | : '' | 266 | } else if (!p.multiple) { |
264 | : _tokens | 267 | return '' |
268 | } | ||
269 | return _tokens | ||
265 | } | 270 | } |
266 | }) | 271 | }) |
267 | } | 272 | } |
diff --git a/shared/models/videos/abuse/video-abuse.model.ts b/shared/models/videos/abuse/video-abuse.model.ts index bbef7f4f9..f2c2cdc41 100644 --- a/shared/models/videos/abuse/video-abuse.model.ts +++ b/shared/models/videos/abuse/video-abuse.model.ts | |||
@@ -23,7 +23,7 @@ export interface VideoAbuse { | |||
23 | } | 23 | } |
24 | 24 | ||
25 | createdAt: Date | 25 | createdAt: Date |
26 | updatedAt?: Date | 26 | updatedAt: Date |
27 | 27 | ||
28 | count?: number | 28 | count?: number |
29 | nth?: number | 29 | nth?: number |