From 25a42e293be90d35afad2096e9db2fa3d617d855 Mon Sep 17 00:00:00 2001 From: Rigel Kent Date: Sun, 3 May 2020 23:01:57 +0200 Subject: [PATCH] Fix rowsPerPage change, add filter clear button, update video-abuse-list search query param dynamically --- .../followers-list.component.html | 12 +++-- .../following-list.component.html | 4 +- .../video-redundancies-list.component.html | 2 +- .../instance-account-blocklist.component.html | 4 +- .../instance-server-blocklist.component.html | 4 +- .../video-abuse-details.component.html | 4 +- .../video-abuse-details.component.ts | 11 ++-- .../video-abuse-list.component.html | 18 +++---- .../video-abuse-list.component.ts | 51 ++++++++++++++----- .../video-blacklist-list.component.html | 6 ++- .../+admin/system/jobs/jobs.component.html | 2 +- .../user-edit/user-password.component.scss | 4 ++ .../users/user-list/user-list.component.html | 8 +-- ...-account-video-channel-edit.component.scss | 4 ++ .../my-account-video-imports.component.html | 2 +- .../+signup/+register/register.component.scss | 4 ++ client/src/app/shared/rest/rest-table.ts | 21 +++++++- client/src/sass/application.scss | 5 ++ client/src/sass/bootstrap.scss | 41 +++++++++++++-- client/src/sass/primeng-custom.scss | 3 +- server/models/utils.ts | 27 ++++++---- .../models/videos/abuse/video-abuse.model.ts | 2 +- 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 @@ >
- +
+ + + Clear filters +
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 @@ >
-
+
+ + Clear filters
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 @@ 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 @@ >
-
+
+ + Clear filters
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 @@ >
-
+
+ + Clear filters
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 @@ alt="Avatar" >
- {{ createByString(videoAbuse.reporterAccount) }} + {{ videoAbuse.reporterAccount.nameWithHost }}
@@ -34,7 +34,7 @@ alt="Avatar" >
- {{ videoAbuse.video.channel.ownerAccount ? createByString(videoAbuse.video.channel.ownerAccount) : '' }} + {{ videoAbuse.video.channel.ownerAccount ? videoAbuse.video.channel.ownerAccount.nameWithHost : '' }}
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 @@ -import { Component, ViewEncapsulation, Input } from '@angular/core' -import { VideoAbuse } from '../../../../../../shared' +import { Component, Input } from '@angular/core' import { Account } from '@app/shared/account/account.model' +import { Actor } from '@app/shared/actor/actor.model' +import { ProcessedVideoAbuse } from './video-abuse-list.component' @Component({ selector: 'my-video-abuse-details', @@ -8,9 +9,9 @@ import { Account } from '@app/shared/account/account.model' styleUrls: [ '../moderation.component.scss' ] }) export class VideoAbuseDetailsComponent { - @Input() videoAbuse: VideoAbuse + @Input() videoAbuse: ProcessedVideoAbuse - createByString (account: Account) { - return Account.CREATE_BY_STRING(account.name, account.host) + switchToDefaultAvatar ($event: Event) { + ($event.target as HTMLImageElement).src = Actor.GET_DEFAULT_AVATAR_URL() } } 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 @@ [sortField]="sort.field" [sortOrder]="sort.order" (onLazyLoad)="loadLazy($event)" dataKey="id" [resizableColumns]="true" [showCurrentPageReport]="true" i18n-currentPageReportTemplate currentPageReportTemplate="Showing {{'{first}'}} to {{'{last}'}} of {{'{totalRecords}'}} reports" - (onPage)="onPage()" [expandedRowKeys]="expandedRows" + (onPage)="onPage($event)" [expandedRowKeys]="expandedRows" >
- @@ -68,7 +64,7 @@ >
{{ videoAbuse.reporterAccount.displayName }} - {{ createByString(videoAbuse.reporterAccount) }} + {{ videoAbuse.reporterAccount.nameWithHost }}
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' import { DomSanitizer } from '@angular/platform-browser' import { BlocklistService } from '@app/shared/blocklist' import { VideoService } from '@app/shared/video/video.service' -import { ActivatedRoute } from '@angular/router' +import { ActivatedRoute, Params, Router } from '@angular/router' import { filter } from 'rxjs/operators' +export type ProcessedVideoAbuse = VideoAbuse & { + moderationCommentHtml?: string, + reasonHtml?: string + embedHtml?: string + updatedAt?: Date + // override bare server-side definitions with rich client-side definitions + reporterAccount: Account + video: VideoAbuse['video'] & { + channel: VideoAbuse['video']['channel'] & { + ownerAccount: Account + } + } +} + @Component({ selector: 'my-video-abuse-list', templateUrl: './video-abuse-list.component.html', @@ -27,7 +41,7 @@ import { filter } from 'rxjs/operators' export class VideoAbuseListComponent extends RestTable implements OnInit, AfterViewInit { @ViewChild('moderationCommentModal', { static: true }) moderationCommentModal: ModerationCommentModalComponent - videoAbuses: (VideoAbuse & { moderationCommentHtml?: string, reasonHtml?: string })[] = [] + videoAbuses: ProcessedVideoAbuse[] = [] totalRecords = 0 sort: SortMeta = { field: 'createdAt', order: 1 } pagination: RestPagination = { count: this.rowsPerPage, start: 0 } @@ -44,7 +58,8 @@ export class VideoAbuseListComponent extends RestTable implements OnInit, AfterV private i18n: I18n, private markdownRenderer: MarkdownService, private sanitizer: DomSanitizer, - private route: ActivatedRoute + private route: ActivatedRoute, + private router: Router ) { super() @@ -212,15 +227,24 @@ export class VideoAbuseListComponent extends RestTable implements OnInit, AfterV this.loadData() } - createByString (account: Account) { - return Account.CREATE_BY_STRING(account.name, account.host) + /* Table filter functions */ + onAbuseSearch (event: Event) { + this.onSearch(event) + this.setQueryParams((event.target as HTMLInputElement).value) + } + + setQueryParams (search: string) { + const queryParams: Params = {} + if (search) Object.assign(queryParams, { search }) + this.router.navigate([ '/admin/moderation/video-abuses/list' ], { queryParams }) } - setTableFilter (filter: string) { - // FIXME: cannot use ViewChild, so create a component for the filter input - const filterInput = document.getElementById('table-filter') as HTMLInputElement - if (filterInput) filterInput.value = filter + resetTableFilter () { + this.setTableFilter('') + this.setQueryParams('') + this.resetSearch() } + /* END Table filter functions */ isVideoAbuseAccepted (videoAbuse: VideoAbuse) { return videoAbuse.state.id === VideoAbuseState.ACCEPTED @@ -279,17 +303,20 @@ export class VideoAbuseListComponent extends RestTable implements OnInit, AfterV }).subscribe( async resultList => { this.totalRecords = resultList.total + this.videoAbuses = [] - this.videoAbuses = resultList.data - - for (const abuse of this.videoAbuses) { + for (const abuse of resultList.data) { Object.assign(abuse, { reasonHtml: await this.toHtml(abuse.reason), moderationCommentHtml: await this.toHtml(abuse.moderationComment), embedHtml: this.sanitizer.bypassSecurityTrustHtml(this.getVideoEmbed(abuse)), reporterAccount: new Account(abuse.reporterAccount) }) + + if (abuse.video.channel?.ownerAccount) abuse.video.channel.ownerAccount = new Account(abuse.video.channel.ownerAccount) if (abuse.updatedAt === abuse.createdAt) delete abuse.updatedAt + + this.videoAbuses.push(abuse as ProcessedVideoAbuse) } }, 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 @@ [sortField]="sort.field" [sortOrder]="sort.order" (onLazyLoad)="loadLazy($event)" dataKey="id" [showCurrentPageReport]="true" i18n-currentPageReportTemplate currentPageReportTemplate="Showing {{'{first}'}} to {{'{last}'}} of {{'{totalRecords}'}} blacklisted videos" - (onPage)="onPage()" [expandedRowKeys]="expandedRows" + (onPage)="onPage($event)" [expandedRowKeys]="expandedRows" >
-
+
+ + Clear filters
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 @@ 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] { margin-top: 10px; } + +.input-group-append { + height: 30px; +} 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 @@
@@ -25,11 +25,13 @@
-
+
+ + Clear filters
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 { @include peertube-input-group(fit-content); } +.input-group-append { + height: 30px; +} + input { &[type=text] { @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 @@ 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 @@ @include peertube-input-group(400px); } +.input-group-append { + height: 30px; +} + input:not([type=submit]) { @include peertube-input-text(400px); 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 { this.searchStream.next(target.value) } - onPage () { + onPage (event: { first: number, rows: number }) { + if (this.rowsPerPage !== event.rows) { + this.rowsPerPage = event.rows + this.pagination = { + start: event.first, + count: this.rowsPerPage + } + this.loadData() + } this.expandedRows = {} } + setTableFilter (filter: string) { + // FIXME: cannot use ViewChild, so create a component for the filter input + const filterInput = document.getElementById('table-filter') as HTMLInputElement + if (filterInput) filterInput.value = filter + } + + resetSearch () { + this.searchStream.next('') + this.setTableFilter('') + } + protected abstract loadData (): void 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 { .peertube-select-container { width: 100% !important; } + + .caption input[type=text] { + width: unset !important; + flex-grow: 1; + } } } } 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/'; } /* rules for dropdowns excepts when in button group, to avoid impacting the dropdown-toggle */ -.dropdown:not(.btn-group):not(.dropdown-root):not(.action-dropdown) { +.dropdown:not(.btn-group):not(.dropdown-root):not(.action-dropdown):not(.input-group-prepend) { z-index: z(dropdown) !important; &.list-overflow-menu, @@ -270,10 +270,9 @@ ngb-tooltip-window { & > .form-control { flex: initial; } - - .input-group-prepend, - .input-group-append { - height: 30px; + input.form-control { + width: unset !important; + flex-grow: 1; } .input-group-prepend + input { @@ -281,3 +280,35 @@ ngb-tooltip-window { border-bottom-left-radius: 0 !important; } } + +.has-feedback.has-clear { + position: relative; + + input { + padding-right: 1.5rem !important; + } + + .form-control-clear { + color: rgba(0, 0, 0, 0.4); + /* + * Enable pointer events as they have been disabled since Bootstrap 3.3 + * See https://github.com/twbs/bootstrap/pull/14104 + */ + pointer-events: all; + display: flex; + justify-content: center; + align-items: center; + position: absolute; + right: .5rem; + height: 95%; + + &:hover { + color: rgba(0, 0, 0, 0.7); + cursor: pointer; + } + } + + input:placeholder-shown + .form-control-clear { + display: none; + } +} 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 { .caption { height: 40px; - display: flex; + width: 100%; + display: inline-flex; align-items: center; .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 { [key: string]: string | { prefix: string, handler: Function, multiple?: boolean } } -function parseQueryStringFilter (q: string, prefixes: QueryStringFilterPrefixes) { +function parseQueryStringFilter (q: string, prefixes: QueryStringFilterPrefixes): { + search: string + [key: string]: string | number | string[] | number[] +} { const tokens = q // tokenize only if we have a querystring - ? [].concat.apply([], q.split('"').map((v, i) => i % 2 ? v : v.split(' '))).filter(Boolean) + ? [].concat.apply([], q.split('"').map((v, i) => i % 2 ? v : v.split(' '))).filter(Boolean) // split by space unless using double quotes : [] // TODO: when Typescript supports Object.fromEntries, replace with the Object method @@ -252,16 +255,18 @@ function parseQueryStringFilter (q: string, prefixes: QueryStringFilterPrefixes) } })).join(' '), // filters defined in prefixes are added under their own name - ...objectMap(prefixes, v => { - if (typeof v === "string") { - return tokens.filter(e => e.startsWith(v)).map(e => e.slice(v.length)) + ...objectMap(prefixes, p => { + if (typeof p === "string") { + return tokens.filter(e => e.startsWith(p)).map(e => e.slice(p.length)) // we keep the matched item, and remove its prefix } else { - const _tokens = tokens.filter(e => e.startsWith(v.prefix)).map(e => e.slice(v.prefix.length)).map(v.handler) - return !v.multiple - ? _tokens.length > 0 - ? _tokens[0] - : '' - : _tokens + const _tokens = tokens.filter(e => e.startsWith(p.prefix)).map(e => e.slice(p.prefix.length)).map(p.handler) + // multiple is false by default, meaning we usually just keep the first occurence of a given prefix + if (!p.multiple && _tokens.length > 0) { + return _tokens[0] + } else if (!p.multiple) { + return '' + } + return _tokens } }) } 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 { } createdAt: Date - updatedAt?: Date + updatedAt: Date count?: number nth?: number -- 2.41.0