From 0d3a2982a9b29bd66a05e4b36e606518d4c09959 Mon Sep 17 00:00:00 2001 From: Rigel Kent Date: Sat, 2 May 2020 22:38:18 +0200 Subject: [PATCH] Switching to a named filters/single input on video-abuse --- .../moderation/moderation.component.scss | 1 + .../video-abuse-list.component.html | 40 ++++++++--- .../video-abuse-list.component.scss | 8 +++ .../user-edit/user-password.component.scss | 4 -- ...-account-video-channel-edit.component.scss | 4 -- .../+signup/+register/register.component.scss | 4 -- .../subscribe-button.component.scss | 4 -- client/src/sass/bootstrap.scss | 20 +++++- client/src/sass/primeng-custom.scss | 4 ++ server/models/utils.ts | 51 +++++++++++++- server/models/video/video-abuse.ts | 66 ++++++++++++++++++- 11 files changed, 175 insertions(+), 31 deletions(-) diff --git a/client/src/app/+admin/moderation/moderation.component.scss b/client/src/app/+admin/moderation/moderation.component.scss index cf06401cf..26c2a30d4 100644 --- a/client/src/app/+admin/moderation/moderation.component.scss +++ b/client/src/app/+admin/moderation/moderation.component.scss @@ -12,6 +12,7 @@ input { @include peertube-input-text(250px); + flex-grow: 1; } } 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 2f6e12d1c..b55b18333 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 @@ -7,10 +7,32 @@ @@ -100,7 +122,7 @@ @@ -118,7 +140,7 @@
Reporter - -
- + + {videoAbuse.countReportsForReporter, plural, =1 {1 report} other {{{ videoAbuse.countReportsForReporter }} reports}} @@ -149,7 +171,7 @@ {{ videoAbuse.video.channel.ownerAccount ? createByString(videoAbuse.video.channel.ownerAccount) : '' }} - + {videoAbuse.countReportsForReportee, plural, =1 {1 report} other {{{ videoAbuse.countReportsForReportee }} reports}} diff --git a/client/src/app/+admin/moderation/video-abuse-list/video-abuse-list.component.scss b/client/src/app/+admin/moderation/video-abuse-list/video-abuse-list.component.scss index d6bc34935..8eee15b64 100644 --- a/client/src/app/+admin/moderation/video-abuse-list/video-abuse-list.component.scss +++ b/client/src/app/+admin/moderation/video-abuse-list/video-abuse-list.component.scss @@ -13,3 +13,11 @@ .video-abuse-states .glyphicon-comment { margin-left: 0.5rem; } + +.input-group { + @include peertube-input-group(300px); + + .dropdown-toggle::after { + margin-left: 0; + } +} 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 217d585af..5cd93f6af 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,7 +16,3 @@ input[type=submit] { margin-top: 10px; } - -.input-group-append { - height: 30px; -} 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 8f8af655c..ba27ee7ff 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,10 +19,6 @@ 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/+signup/+register/register.component.scss b/client/src/app/+signup/+register/register.component.scss index e135b5cb4..cc60ef524 100644 --- a/client/src/app/+signup/+register/register.component.scss +++ b/client/src/app/+signup/+register/register.component.scss @@ -58,10 +58,6 @@ @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/user-subscription/subscribe-button.component.scss b/client/src/app/shared/user-subscription/subscribe-button.component.scss index 5283a6cc3..b739c5ae2 100644 --- a/client/src/app/shared/user-subscription/subscribe-button.component.scss +++ b/client/src/app/shared/user-subscription/subscribe-button.component.scss @@ -88,10 +88,6 @@ } } - .dropdown-header { - padding-left: 1rem; - } - ::ng-deep form { padding: 0.25rem 1rem; } diff --git a/client/src/sass/bootstrap.scss b/client/src/sass/bootstrap.scss index 377c85070..50f1dafed 100644 --- a/client/src/sass/bootstrap.scss +++ b/client/src/sass/bootstrap.scss @@ -41,6 +41,10 @@ $icon-font-path: '~@neos21/bootstrap3-glyphicons/assets/fonts/'; box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 1px 5px 0 rgba(0, 0, 0, 0.12), 0 3px 1px -2px rgba(0, 0, 0, 0.2); font-size: 15px; + .dropdown-header { + padding-left: 1rem; + } + .dropdown-item { padding: 3px 15px; @@ -262,6 +266,18 @@ ngb-tooltip-window { } } -.input-group > .form-control { - flex: initial; +.input-group { + & > .form-control { + flex: initial; + } + + .input-group-prepend, + .input-group-append { + height: 30px; + } + + .input-group-prepend + input { + border-top-left-radius: 0 !important; + border-bottom-left-radius: 0 !important; + } } diff --git a/client/src/sass/primeng-custom.scss b/client/src/sass/primeng-custom.scss index b3cd7cf51..eab2b2dfd 100644 --- a/client/src/sass/primeng-custom.scss +++ b/client/src/sass/primeng-custom.scss @@ -32,6 +32,10 @@ p-table { height: 40px; display: flex; align-items: center; + + .input-group-text { + background-color: transparent; + } } } diff --git a/server/models/utils.ts b/server/models/utils.ts index bdf2291f0..3e3825b32 100644 --- a/server/models/utils.ts +++ b/server/models/utils.ts @@ -219,6 +219,54 @@ function searchAttribute (sourceField, targetField) { } } +interface QueryStringFilterPrefixes { + [key: string]: string | { prefix: string, handler: Function, multiple?: boolean } +} + +function parseQueryStringFilter (q: string, prefixes: QueryStringFilterPrefixes) { + const tokens = q // tokenize only if we have a querystring + ? [].concat.apply([], q.split('"').map((v, i) => i % 2 ? v : v.split(' '))).filter(Boolean) + : [] + + // TODO: when Typescript supports Object.fromEntries, replace with the Object method + function fromEntries (entries: [keyof T, T[keyof T]][]): T { + return entries.reduce( + (acc, [ key, value ]) => ({ ...acc, [key]: value }), + {} as T + ) + } + + const objectMap = (obj, fn) => fromEntries( + Object.entries(obj).map( + ([ k, v ], i) => [ k, fn(v, k, i) ] + ) + ) + + return { + // search is the querystring minus defined filters + search: tokens.filter(e => !Object.values(prefixes).some(p => { + if (typeof p === "string") { + return e.startsWith(p) + } else { + return e.startsWith(p.prefix) + } + })).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)) + } 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 + } + }) + } +} + // --------------------------------------------------------------------------- export { @@ -241,7 +289,8 @@ export { getFollowsSort, buildDirectionAndField, createSafeIn, - searchAttribute + searchAttribute, + parseQueryStringFilter } // --------------------------------------------------------------------------- diff --git a/server/models/video/video-abuse.ts b/server/models/video/video-abuse.ts index 628f1caa6..b1f8fed90 100644 --- a/server/models/video/video-abuse.ts +++ b/server/models/video/video-abuse.ts @@ -9,7 +9,7 @@ import { isVideoAbuseStateValid } from '../../helpers/custom-validators/video-abuses' import { AccountModel } from '../account/account' -import { buildBlockedAccountSQL, getSort, throwIfNotValid, searchAttribute } from '../utils' +import { buildBlockedAccountSQL, getSort, throwIfNotValid, searchAttribute, parseQueryStringFilter } from '../utils' import { VideoModel } from './video' import { VideoAbuseState, VideoDetails } from '../../../shared' import { CONSTRAINTS_FIELDS, VIDEO_ABUSE_STATES } from '../../initializers/constants' @@ -26,10 +26,17 @@ export enum ScopeNames { @Scopes(() => ({ [ScopeNames.FOR_API]: (options: { + // search search?: string searchReporter?: string + searchReportee?: string searchVideo?: string searchVideoChannel?: string + // filters + id?: number + state?: VideoAbuseState + is?: any + // accountIds serverAccountId: number userAccountId: number }) => { @@ -71,6 +78,24 @@ export enum ScopeNames { }) } + if (options.id) { + where = Object.assign(where, { + id: options.id + }) + } + + if (options.state) { + where = Object.assign(where, { + state: options.state + }) + } + + if (options.is) { + where = Object.assign(where, { + ...options.is + }) + } + return { attributes: { include: [ @@ -167,7 +192,13 @@ export enum ScopeNames { }, { model: VideoChannelModel.scope({ method: [ VideoChannelScopeNames.SUMMARY, { withAccount: true } as SummaryOptions ] }), - where: searchAttribute(options.searchVideoChannel, 'name') + where: searchAttribute(options.searchVideoChannel, 'name'), + include: [ + { + model: AccountModel, + where: searchAttribute(options.searchReportee, 'name') + } + ] }, { attributes: [ 'id', 'reason', 'unfederated' ], @@ -280,7 +311,36 @@ export class VideoAbuseModel extends Model { } const filters = { - search, + ...parseQueryStringFilter(search, { + id: { + prefix: '#', + handler: v => v + }, + state: { + prefix: 'state:', + handler: v => { + if (v === "accepted") return VideoAbuseState.ACCEPTED + if (v === "pending") return VideoAbuseState.PENDING + if (v === "rejected") return VideoAbuseState.REJECTED + return undefined + } + }, + is: { + prefix: 'is:', + handler: v => { + if (v === "deleted") return { deletedVideo: { [Op.not]: null } } + return undefined + } + }, + searchReporter: { + prefix: 'reporter:', + handler: v => v + }, + searchReportee: { + prefix: 'reportee:', + handler: v => v + } + }), serverAccountId, userAccountId }