-
@@ -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,16 +227,25 @@ 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)
}
- 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
+ setQueryParams (search: string) {
+ const queryParams: Params = {}
+ if (search) Object.assign(queryParams, { search })
+ this.router.navigate([ '/admin/moderation/video-abuses/list' ], { queryParams })
}
+ 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"
>
-
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 @@
0" [totalRecords]="totalRecords" [rows]="rowsPerPage" dataKey="uniqId"
[sortField]="sort.field" [sortOrder]="sort.order" (onLazyLoad)="loadLazy($event)" [first]="pagination.start"
- [tableStyle]="{'table-layout':'auto'}" (onPage)="onPage()" [expandedRowKeys]="expandedRows"
+ [tableStyle]="{'table-layout':'auto'}" (onPage)="onPage($event)" [expandedRowKeys]="expandedRows"
>
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 @@
0" [totalRecords]="totalRecords" [rows]="rowsPerPage"
+ [value]="users" [lazy]="true" [paginator]="totalRecords > 0" [totalRecords]="totalRecords" [rows]="rowsPerPage" [rowsPerPageOptions]="rowsPerPageOptions"
[sortField]="sort.field" [sortOrder]="sort.order" (onLazyLoad)="loadLazy($event)" dataKey="id"
[(selection)]="selectedUsers"
[showCurrentPageReport]="true" i18n-currentPageReportTemplate
currentPageReportTemplate="Showing {{'{first}'}} to {{'{last}'}} of {{'{totalRecords}'}} users"
- (onPage)="onPage()" [expandedRowKeys]="expandedRows"
+ (onPage)="onPage($event)" [expandedRowKeys]="expandedRows"
>
@@ -25,11 +25,13 @@
-
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 @@
0" [totalRecords]="totalRecords" [rows]="rowsPerPage"
[sortField]="sort.field" [sortOrder]="sort.order" (onLazyLoad)="loadLazy($event)" dataKey="id"
- (onPage)="onPage()" [expandedRowKeys]="expandedRows"
+ (onPage)="onPage($event)" [expandedRowKeys]="expandedRows"
>
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