mirror of https://github.com/Chocobozzz/PeerTube
Add ability to filter my videos by live
parent
dfcb6f50a6
commit
1fd61899ea
|
@ -3,4 +3,4 @@
|
||||||
<ng-container i18n>Reports</ng-container>
|
<ng-container i18n>Reports</ng-container>
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
<my-abuse-list-table viewType="admin" baseRoute="/admin/moderation/abuses/list"></my-abuse-list-table>
|
<my-abuse-list-table viewType="admin"></my-abuse-list-table>
|
||||||
|
|
|
@ -13,25 +13,7 @@
|
||||||
<ng-template pTemplate="caption">
|
<ng-template pTemplate="caption">
|
||||||
<div class="caption">
|
<div class="caption">
|
||||||
<div class="ml-auto">
|
<div class="ml-auto">
|
||||||
<div class="input-group has-feedback has-clear">
|
<my-advanced-input-filter [filters]="inputFilters" (search)="onSearch($event)" (resetTableFilter)="resetTableFilter()"></my-advanced-input-filter>
|
||||||
<div class="input-group-prepend c-hand" ngbDropdown placement="bottom-left auto" container="body">
|
|
||||||
<div class="input-group-text" ngbDropdownToggle>
|
|
||||||
<span class="caret" aria-haspopup="menu" role="button"></span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div role="menu" ngbDropdownMenu>
|
|
||||||
<h6 class="dropdown-header" i18n>Advanced block filters</h6>
|
|
||||||
<a [routerLink]="[ '/admin/moderation/video-blocks/list' ]" [queryParams]="{ 'search': 'type:auto' }" class="dropdown-item" i18n>Automatic blocks</a>
|
|
||||||
<a [routerLink]="[ '/admin/moderation/video-blocks/list' ]" [queryParams]="{ 'search': 'type:manual' }" class="dropdown-item" i18n>Manual blocks</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<input
|
|
||||||
type="text" name="table-filter" id="table-filter" i18n-placeholder placeholder="Filter..."
|
|
||||||
(keyup)="onSearch($event)"
|
|
||||||
>
|
|
||||||
<a class="glyphicon glyphicon-remove-sign form-control-feedback form-control-clear" (click)="resetTableFilter()"></a>
|
|
||||||
<span class="sr-only" i18n>Clear filters</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
|
|
@ -6,6 +6,7 @@ import { AfterViewInit, Component, OnInit } from '@angular/core'
|
||||||
import { DomSanitizer } from '@angular/platform-browser'
|
import { DomSanitizer } from '@angular/platform-browser'
|
||||||
import { ActivatedRoute, Params, Router } from '@angular/router'
|
import { ActivatedRoute, Params, Router } from '@angular/router'
|
||||||
import { ConfirmService, MarkdownService, Notifier, RestPagination, RestTable, ServerService } from '@app/core'
|
import { ConfirmService, MarkdownService, Notifier, RestPagination, RestTable, ServerService } from '@app/core'
|
||||||
|
import { AdvancedInputFilter } from '@app/shared/shared-forms'
|
||||||
import { DropdownAction, Video, VideoService } from '@app/shared/shared-main'
|
import { DropdownAction, Video, VideoService } from '@app/shared/shared-main'
|
||||||
import { VideoBlockService } from '@app/shared/shared-moderation'
|
import { VideoBlockService } from '@app/shared/shared-moderation'
|
||||||
import { VideoBlacklist, VideoBlacklistType } from '@shared/models'
|
import { VideoBlacklist, VideoBlacklistType } from '@shared/models'
|
||||||
|
@ -24,6 +25,17 @@ export class VideoBlockListComponent extends RestTable implements OnInit, AfterV
|
||||||
|
|
||||||
videoBlocklistActions: DropdownAction<VideoBlacklist>[][] = []
|
videoBlocklistActions: DropdownAction<VideoBlacklist>[][] = []
|
||||||
|
|
||||||
|
inputFilters: AdvancedInputFilter[] = [
|
||||||
|
{
|
||||||
|
queryParams: { 'search': 'type:auto' },
|
||||||
|
label: $localize`Automatic blocks`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
queryParams: { 'search': 'type:manual' },
|
||||||
|
label: $localize`Manual blocks`
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
constructor (
|
constructor (
|
||||||
protected route: ActivatedRoute,
|
protected route: ActivatedRoute,
|
||||||
protected router: Router,
|
protected router: Router,
|
||||||
|
@ -111,25 +123,6 @@ export class VideoBlockListComponent extends RestTable implements OnInit, AfterV
|
||||||
if (this.search) this.setTableFilter(this.search, false)
|
if (this.search) this.setTableFilter(this.search, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Table filter functions */
|
|
||||||
onBlockSearch (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-blocks/list' ], { queryParams })
|
|
||||||
}
|
|
||||||
|
|
||||||
resetTableFilter () {
|
|
||||||
this.setTableFilter('')
|
|
||||||
this.setQueryParams('')
|
|
||||||
this.resetSearch()
|
|
||||||
}
|
|
||||||
/* END Table filter functions */
|
|
||||||
|
|
||||||
getIdentifier () {
|
getIdentifier () {
|
||||||
return 'VideoBlockListComponent'
|
return 'VideoBlockListComponent'
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,25 +26,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="ml-auto">
|
<div class="ml-auto">
|
||||||
<div class="input-group has-feedback has-clear">
|
<my-advanced-input-filter [filters]="inputFilters" (search)="onSearch($event)" (resetTableFilter)="resetTableFilter()"></my-advanced-input-filter>
|
||||||
<div class="input-group-prepend c-hand" ngbDropdown placement="bottom-left auto" container="body">
|
|
||||||
<div class="input-group-text" ngbDropdownToggle>
|
|
||||||
<span class="caret" aria-haspopup="menu" role="button"></span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div role="menu" ngbDropdownMenu>
|
|
||||||
<h6 class="dropdown-header" i18n>Advanced comments filters</h6>
|
|
||||||
<a [routerLink]="[ '/admin/moderation/video-comments/list' ]" [queryParams]="{ 'search': 'local:true' }" class="dropdown-item" i18n>Local comments</a>
|
|
||||||
<a [routerLink]="[ '/admin/moderation/video-comments/list' ]" [queryParams]="{ 'search': 'local:false' }" class="dropdown-item" i18n>Remote comments</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<input
|
|
||||||
type="text" name="table-filter" id="table-filter" i18n-placeholder placeholder="Filter..."
|
|
||||||
(keyup)="onSearch($event)"
|
|
||||||
>
|
|
||||||
<a class="glyphicon glyphicon-remove-sign form-control-feedback form-control-clear" (click)="resetTableFilter()"></a>
|
|
||||||
<span class="sr-only" i18n>Clear filters</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
|
|
@ -2,6 +2,7 @@ import { SortMeta } from 'primeng/api'
|
||||||
import { AfterViewInit, Component, OnInit } from '@angular/core'
|
import { AfterViewInit, Component, OnInit } from '@angular/core'
|
||||||
import { ActivatedRoute, Router } from '@angular/router'
|
import { ActivatedRoute, Router } from '@angular/router'
|
||||||
import { AuthService, ConfirmService, MarkdownService, Notifier, RestPagination, RestTable } from '@app/core'
|
import { AuthService, ConfirmService, MarkdownService, Notifier, RestPagination, RestTable } from '@app/core'
|
||||||
|
import { AdvancedInputFilter } from '@app/shared/shared-forms'
|
||||||
import { DropdownAction } from '@app/shared/shared-main'
|
import { DropdownAction } from '@app/shared/shared-main'
|
||||||
import { BulkService } from '@app/shared/shared-moderation'
|
import { BulkService } from '@app/shared/shared-moderation'
|
||||||
import { VideoCommentAdmin, VideoCommentService } from '@app/shared/shared-video-comment'
|
import { VideoCommentAdmin, VideoCommentService } from '@app/shared/shared-video-comment'
|
||||||
|
@ -43,6 +44,17 @@ export class VideoCommentListComponent extends RestTable implements OnInit, Afte
|
||||||
selectedComments: VideoCommentAdmin[] = []
|
selectedComments: VideoCommentAdmin[] = []
|
||||||
bulkCommentActions: DropdownAction<VideoCommentAdmin[]>[] = []
|
bulkCommentActions: DropdownAction<VideoCommentAdmin[]>[] = []
|
||||||
|
|
||||||
|
inputFilters: AdvancedInputFilter[] = [
|
||||||
|
{
|
||||||
|
queryParams: { 'search': 'local:true' },
|
||||||
|
label: $localize`Local comments`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
queryParams: { 'search': 'local:false' },
|
||||||
|
label: $localize`Remote comments`
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
get authUser () {
|
get authUser () {
|
||||||
return this.auth.getUser()
|
return this.auth.getUser()
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,24 +22,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="ml-auto">
|
<div class="ml-auto">
|
||||||
<div class="input-group has-feedback has-clear">
|
<my-advanced-input-filter [filters]="inputFilters" (search)="onSearch($event)" (resetTableFilter)="resetTableFilter()"></my-advanced-input-filter>
|
||||||
<div class="input-group-prepend c-hand" ngbDropdown placement="bottom-left auto" container="body">
|
|
||||||
<div class="input-group-text" ngbDropdownToggle>
|
|
||||||
<span class="caret" aria-haspopup="menu" role="button"></span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div role="menu" ngbDropdownMenu>
|
|
||||||
<h6 class="dropdown-header" i18n>Advanced user filters</h6>
|
|
||||||
<a [routerLink]="[ '/admin/users/list' ]" [queryParams]="{ 'search': 'banned:true' }" class="dropdown-item" i18n>Banned users</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<input
|
|
||||||
type="text" name="table-filter" id="table-filter" i18n-placeholder placeholder="Filter..."
|
|
||||||
(keyup)="onSearch($event)"
|
|
||||||
>
|
|
||||||
<a class="glyphicon glyphicon-remove-sign form-control-feedback form-control-clear" (click)="resetTableFilter()"></a>
|
|
||||||
<span class="sr-only" i18n>Clear filters</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -11,6 +11,7 @@ tr.banned > td {
|
||||||
|
|
||||||
.table-email {
|
.table-email {
|
||||||
@include disable-default-a-behaviour;
|
@include disable-default-a-behaviour;
|
||||||
|
|
||||||
color: pvar(--mainForegroundColor);
|
color: pvar(--mainForegroundColor);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -28,14 +29,6 @@ tr.banned > td {
|
||||||
margin-left: 0.1rem;
|
margin-left: 0.1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.caption {
|
|
||||||
justify-content: space-between;
|
|
||||||
|
|
||||||
input {
|
|
||||||
@include peertube-input-text(250px);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
p-tableCheckbox {
|
p-tableCheckbox {
|
||||||
position: relative;
|
position: relative;
|
||||||
top: -2.5px;
|
top: -2.5px;
|
||||||
|
@ -55,18 +48,7 @@ my-global-icon {
|
||||||
|
|
||||||
.progress {
|
.progress {
|
||||||
@include progressbar($small: true);
|
@include progressbar($small: true);
|
||||||
|
|
||||||
width: auto;
|
width: auto;
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.input-group {
|
|
||||||
@include peertube-input-group(300px);
|
|
||||||
|
|
||||||
input {
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dropdown-toggle::after {
|
|
||||||
margin-left: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
import { SortMeta } from 'primeng/api'
|
import { SortMeta } from 'primeng/api'
|
||||||
import { Component, OnInit, ViewChild } from '@angular/core'
|
import { AfterViewInit, Component, OnInit, ViewChild } from '@angular/core'
|
||||||
import { ActivatedRoute, Params, Router } from '@angular/router'
|
import { ActivatedRoute, Router } from '@angular/router'
|
||||||
import { AuthService, ConfirmService, Notifier, RestPagination, RestTable, ServerService, UserService } from '@app/core'
|
import { AuthService, ConfirmService, Notifier, RestPagination, RestTable, ServerService, UserService } from '@app/core'
|
||||||
import { Account, DropdownAction } from '@app/shared/shared-main'
|
import { AdvancedInputFilter } from '@app/shared/shared-forms'
|
||||||
|
import { DropdownAction } from '@app/shared/shared-main'
|
||||||
import { UserBanModalComponent } from '@app/shared/shared-moderation'
|
import { UserBanModalComponent } from '@app/shared/shared-moderation'
|
||||||
import { ServerConfig, User, UserRole } from '@shared/models'
|
import { ServerConfig, User, UserRole } from '@shared/models'
|
||||||
|
|
||||||
|
@ -18,19 +19,28 @@ type UserForList = User & {
|
||||||
templateUrl: './user-list.component.html',
|
templateUrl: './user-list.component.html',
|
||||||
styleUrls: [ './user-list.component.scss' ]
|
styleUrls: [ './user-list.component.scss' ]
|
||||||
})
|
})
|
||||||
export class UserListComponent extends RestTable implements OnInit {
|
export class UserListComponent extends RestTable implements OnInit, AfterViewInit {
|
||||||
@ViewChild('userBanModal', { static: true }) userBanModal: UserBanModalComponent
|
@ViewChild('userBanModal', { static: true }) userBanModal: UserBanModalComponent
|
||||||
|
|
||||||
users: User[] = []
|
users: User[] = []
|
||||||
|
|
||||||
totalRecords = 0
|
totalRecords = 0
|
||||||
sort: SortMeta = { field: 'createdAt', order: 1 }
|
sort: SortMeta = { field: 'createdAt', order: 1 }
|
||||||
pagination: RestPagination = { count: this.rowsPerPage, start: 0 }
|
pagination: RestPagination = { count: this.rowsPerPage, start: 0 }
|
||||||
|
|
||||||
highlightBannedUsers = false
|
highlightBannedUsers = false
|
||||||
|
|
||||||
selectedUsers: User[] = []
|
selectedUsers: User[] = []
|
||||||
bulkUserActions: DropdownAction<User[]>[][] = []
|
bulkUserActions: DropdownAction<User[]>[][] = []
|
||||||
columns: { id: string, label: string }[]
|
columns: { id: string, label: string }[]
|
||||||
|
|
||||||
|
inputFilters: AdvancedInputFilter[] = [
|
||||||
|
{
|
||||||
|
queryParams: { 'search': 'banned:true' },
|
||||||
|
label: $localize`Banned users`
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
private _selectedColumns: string[]
|
private _selectedColumns: string[]
|
||||||
private serverConfig: ServerConfig
|
private serverConfig: ServerConfig
|
||||||
|
|
||||||
|
@ -117,6 +127,10 @@ export class UserListComponent extends RestTable implements OnInit {
|
||||||
this.columns.push({ id: 'lastLoginDate', label: 'Last login' })
|
this.columns.push({ id: 'lastLoginDate', label: 'Last login' })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ngAfterViewInit () {
|
||||||
|
if (this.search) this.setTableFilter(this.search, false)
|
||||||
|
}
|
||||||
|
|
||||||
getIdentifier () {
|
getIdentifier () {
|
||||||
return 'UserListComponent'
|
return 'UserListComponent'
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,4 +3,4 @@
|
||||||
<ng-container i18n>Reports</ng-container>
|
<ng-container i18n>Reports</ng-container>
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
<my-abuse-list-table viewType="user" baseRoute="/my-account/abuses"></my-abuse-list-table>
|
<my-abuse-list-table viewType="user"></my-abuse-list-table>
|
||||||
|
|
|
@ -19,12 +19,7 @@
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
<div class="videos-header d-flex justify-content-between">
|
<div class="videos-header d-flex justify-content-between">
|
||||||
<div class="has-feedback has-clear">
|
<my-advanced-input-filter [filters]="inputFilters" (search)="onSearch($event)" (resetTableFilter)="resetTableFilter()"></my-advanced-input-filter>
|
||||||
<input type="text" placeholder="Search your videos" i18n-placeholder [(ngModel)]="videosSearch"
|
|
||||||
(ngModelChange)="onVideosSearchChanged()" />
|
|
||||||
<a class="glyphicon glyphicon-remove-sign form-control-feedback form-control-clear" (click)="resetSearch()"></a>
|
|
||||||
<span class="sr-only" i18n>Clear filters</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="peertube-select-container peertube-select-button">
|
<div class="peertube-select-container peertube-select-button">
|
||||||
<select [(ngModel)]="sort" (ngModelChange)="onChangeSortColumn()" class="form-control">
|
<select [(ngModel)]="sort" (ngModelChange)="onChangeSortColumn()" class="form-control">
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
import { concat, Observable, Subject } from 'rxjs'
|
import { concat, Observable } from 'rxjs'
|
||||||
import { debounceTime, tap, toArray } from 'rxjs/operators'
|
import { tap, toArray } from 'rxjs/operators'
|
||||||
import { Component, OnInit, ViewChild } from '@angular/core'
|
import { AfterViewInit, Component, OnInit, ViewChild } from '@angular/core'
|
||||||
import { ActivatedRoute, Router } from '@angular/router'
|
import { ActivatedRoute, Router } from '@angular/router'
|
||||||
import { AuthService, ComponentPagination, ConfirmService, Notifier, ScreenService, ServerService, User } from '@app/core'
|
import { AuthService, ComponentPagination, ConfirmService, Notifier, RouteFilter, ScreenService, ServerService, User } from '@app/core'
|
||||||
import { DisableForReuseHook } from '@app/core/routing/disable-for-reuse-hook'
|
import { DisableForReuseHook } from '@app/core/routing/disable-for-reuse-hook'
|
||||||
import { immutableAssign } from '@app/helpers'
|
import { immutableAssign } from '@app/helpers'
|
||||||
|
import { AdvancedInputFilter } from '@app/shared/shared-forms'
|
||||||
import { DropdownAction, Video, VideoService } from '@app/shared/shared-main'
|
import { DropdownAction, Video, VideoService } from '@app/shared/shared-main'
|
||||||
import { LiveStreamInformationComponent } from '@app/shared/shared-video-live'
|
import { LiveStreamInformationComponent } from '@app/shared/shared-video-live'
|
||||||
import { MiniatureDisplayOptions, SelectionType, VideosSelectionComponent } from '@app/shared/shared-video-miniature'
|
import { MiniatureDisplayOptions, SelectionType, VideosSelectionComponent } from '@app/shared/shared-video-miniature'
|
||||||
|
@ -15,7 +16,7 @@ import { VideoChangeOwnershipComponent } from './modals/video-change-ownership.c
|
||||||
templateUrl: './my-videos.component.html',
|
templateUrl: './my-videos.component.html',
|
||||||
styleUrls: [ './my-videos.component.scss' ]
|
styleUrls: [ './my-videos.component.scss' ]
|
||||||
})
|
})
|
||||||
export class MyVideosComponent implements OnInit, DisableForReuseHook {
|
export class MyVideosComponent extends RouteFilter implements OnInit, AfterViewInit, DisableForReuseHook {
|
||||||
@ViewChild('videosSelection', { static: true }) videosSelection: VideosSelectionComponent
|
@ViewChild('videosSelection', { static: true }) videosSelection: VideosSelectionComponent
|
||||||
@ViewChild('videoChangeOwnershipModal', { static: true }) videoChangeOwnershipModal: VideoChangeOwnershipComponent
|
@ViewChild('videoChangeOwnershipModal', { static: true }) videoChangeOwnershipModal: VideoChangeOwnershipComponent
|
||||||
@ViewChild('liveStreamInformationModal', { static: true }) liveStreamInformationModal: LiveStreamInformationComponent
|
@ViewChild('liveStreamInformationModal', { static: true }) liveStreamInformationModal: LiveStreamInformationComponent
|
||||||
|
@ -40,13 +41,18 @@ export class MyVideosComponent implements OnInit, DisableForReuseHook {
|
||||||
videoActions: DropdownAction<{ video: Video }>[] = []
|
videoActions: DropdownAction<{ video: Video }>[] = []
|
||||||
|
|
||||||
videos: Video[] = []
|
videos: Video[] = []
|
||||||
videosSearch: string
|
|
||||||
videosSearchChanged = new Subject<string>()
|
|
||||||
getVideosObservableFunction = this.getVideosObservable.bind(this)
|
getVideosObservableFunction = this.getVideosObservable.bind(this)
|
||||||
sort: VideoSortField = '-publishedAt'
|
sort: VideoSortField = '-publishedAt'
|
||||||
|
|
||||||
user: User
|
user: User
|
||||||
|
|
||||||
|
inputFilters: AdvancedInputFilter[] = [
|
||||||
|
{
|
||||||
|
queryParams: { 'search': 'isLive:true' },
|
||||||
|
label: $localize`Only live videos`
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
constructor (
|
constructor (
|
||||||
protected router: Router,
|
protected router: Router,
|
||||||
protected serverService: ServerService,
|
protected serverService: ServerService,
|
||||||
|
@ -57,6 +63,8 @@ export class MyVideosComponent implements OnInit, DisableForReuseHook {
|
||||||
private confirmService: ConfirmService,
|
private confirmService: ConfirmService,
|
||||||
private videoService: VideoService
|
private videoService: VideoService
|
||||||
) {
|
) {
|
||||||
|
super()
|
||||||
|
|
||||||
this.titlePage = $localize`My videos`
|
this.titlePage = $localize`My videos`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -65,20 +73,16 @@ export class MyVideosComponent implements OnInit, DisableForReuseHook {
|
||||||
|
|
||||||
this.user = this.authService.getUser()
|
this.user = this.authService.getUser()
|
||||||
|
|
||||||
this.videosSearchChanged
|
this.initSearch()
|
||||||
.pipe(debounceTime(500))
|
this.listenToSearchChange()
|
||||||
.subscribe(() => {
|
}
|
||||||
|
|
||||||
|
ngAfterViewInit () {
|
||||||
|
if (this.search) this.setTableFilter(this.search, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
loadData () {
|
||||||
this.videosSelection.reloadVideos()
|
this.videosSelection.reloadVideos()
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
resetSearch () {
|
|
||||||
this.videosSearch = ''
|
|
||||||
this.onVideosSearchChanged()
|
|
||||||
}
|
|
||||||
|
|
||||||
onVideosSearchChanged () {
|
|
||||||
this.videosSearchChanged.next()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onChangeSortColumn () {
|
onChangeSortColumn () {
|
||||||
|
@ -96,7 +100,7 @@ export class MyVideosComponent implements OnInit, DisableForReuseHook {
|
||||||
getVideosObservable (page: number) {
|
getVideosObservable (page: number) {
|
||||||
const newPagination = immutableAssign(this.pagination, { currentPage: page })
|
const newPagination = immutableAssign(this.pagination, { currentPage: page })
|
||||||
|
|
||||||
return this.videoService.getMyVideos(newPagination, this.sort, this.videosSearch)
|
return this.videoService.getMyVideos(newPagination, this.sort, this.search)
|
||||||
.pipe(
|
.pipe(
|
||||||
tap(res => this.pagination.totalItems = res.total)
|
tap(res => this.pagination.totalItems = res.total)
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
import * as debug from 'debug'
|
import * as debug from 'debug'
|
||||||
import { LazyLoadEvent, SortMeta } from 'primeng/api'
|
import { LazyLoadEvent, SortMeta } from 'primeng/api'
|
||||||
import { Subject } from 'rxjs'
|
import { Subject } from 'rxjs'
|
||||||
import { debounceTime, distinctUntilChanged } from 'rxjs/operators'
|
import { ActivatedRoute, Router } from '@angular/router'
|
||||||
import { ActivatedRoute, Params, Router } from '@angular/router'
|
|
||||||
import { peertubeLocalStorage } from '@root-helpers/peertube-web-storage'
|
import { peertubeLocalStorage } from '@root-helpers/peertube-web-storage'
|
||||||
|
import { RouteFilter } from '../routing'
|
||||||
import { RestPagination } from './rest-pagination'
|
import { RestPagination } from './rest-pagination'
|
||||||
|
|
||||||
const logger = debug('peertube:tables:RestTable')
|
const logger = debug('peertube:tables:RestTable')
|
||||||
|
|
||||||
export abstract class RestTable {
|
export abstract class RestTable extends RouteFilter {
|
||||||
|
|
||||||
abstract totalRecords: number
|
abstract totalRecords: number
|
||||||
abstract sort: SortMeta
|
abstract sort: SortMeta
|
||||||
|
@ -19,8 +19,6 @@ export abstract class RestTable {
|
||||||
rowsPerPage = this.rowsPerPageOptions[0]
|
rowsPerPage = this.rowsPerPageOptions[0]
|
||||||
expandedRows = {}
|
expandedRows = {}
|
||||||
|
|
||||||
baseRoute: string
|
|
||||||
|
|
||||||
protected searchStream: Subject<string>
|
protected searchStream: Subject<string>
|
||||||
|
|
||||||
protected route: ActivatedRoute
|
protected route: ActivatedRoute
|
||||||
|
@ -66,55 +64,6 @@ export abstract class RestTable {
|
||||||
peertubeLocalStorage.setItem(this.getSortLocalStorageKey(), JSON.stringify(this.sort))
|
peertubeLocalStorage.setItem(this.getSortLocalStorageKey(), JSON.stringify(this.sort))
|
||||||
}
|
}
|
||||||
|
|
||||||
initSearch () {
|
|
||||||
this.searchStream = new Subject()
|
|
||||||
|
|
||||||
this.searchStream
|
|
||||||
.pipe(
|
|
||||||
debounceTime(400),
|
|
||||||
distinctUntilChanged()
|
|
||||||
)
|
|
||||||
.subscribe(search => {
|
|
||||||
this.search = search
|
|
||||||
|
|
||||||
logger('On search %s.', this.search)
|
|
||||||
|
|
||||||
this.loadData()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
onSearch (event: Event) {
|
|
||||||
const target = event.target as HTMLInputElement
|
|
||||||
this.searchStream.next(target.value)
|
|
||||||
|
|
||||||
this.setQueryParams((event.target as HTMLInputElement).value)
|
|
||||||
}
|
|
||||||
|
|
||||||
setQueryParams (search: string) {
|
|
||||||
if (!this.baseRoute) return
|
|
||||||
|
|
||||||
const queryParams: Params = {}
|
|
||||||
|
|
||||||
if (search) Object.assign(queryParams, { search })
|
|
||||||
this.router.navigate([ this.baseRoute ], { queryParams })
|
|
||||||
}
|
|
||||||
|
|
||||||
resetTableFilter () {
|
|
||||||
this.setTableFilter('')
|
|
||||||
this.setQueryParams('')
|
|
||||||
this.resetSearch()
|
|
||||||
}
|
|
||||||
|
|
||||||
listenToSearchChange () {
|
|
||||||
this.route.queryParams
|
|
||||||
.subscribe(params => {
|
|
||||||
this.search = params.search || ''
|
|
||||||
|
|
||||||
// Primeng table will run an event to load data
|
|
||||||
this.setTableFilter(this.search)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
onPage (event: { first: number, rows: number }) {
|
onPage (event: { first: number, rows: number }) {
|
||||||
logger('On page %o.', event)
|
logger('On page %o.', event)
|
||||||
|
|
||||||
|
@ -131,21 +80,6 @@ export abstract class RestTable {
|
||||||
this.expandedRows = {}
|
this.expandedRows = {}
|
||||||
}
|
}
|
||||||
|
|
||||||
setTableFilter (filter: string, triggerEvent = true) {
|
|
||||||
// FIXME: cannot use ViewChild, so create a component for the filter input
|
|
||||||
const filterInput = document.getElementById('table-filter') as HTMLInputElement
|
|
||||||
if (!filterInput) return
|
|
||||||
|
|
||||||
filterInput.value = filter
|
|
||||||
|
|
||||||
if (triggerEvent) filterInput.dispatchEvent(new Event('keyup'))
|
|
||||||
}
|
|
||||||
|
|
||||||
resetSearch () {
|
|
||||||
this.searchStream.next('')
|
|
||||||
this.setTableFilter('')
|
|
||||||
}
|
|
||||||
|
|
||||||
protected abstract loadData (): void
|
protected abstract loadData (): void
|
||||||
|
|
||||||
private getSortLocalStorageKey () {
|
private getSortLocalStorageKey () {
|
||||||
|
|
|
@ -5,6 +5,7 @@ export * from './login-guard.service'
|
||||||
export * from './menu-guard.service'
|
export * from './menu-guard.service'
|
||||||
export * from './preload-selected-modules-list'
|
export * from './preload-selected-modules-list'
|
||||||
export * from './redirect.service'
|
export * from './redirect.service'
|
||||||
|
export * from './route-filter'
|
||||||
export * from './server-config-resolver.service'
|
export * from './server-config-resolver.service'
|
||||||
export * from './unlogged-guard.service'
|
export * from './unlogged-guard.service'
|
||||||
export * from './user-right-guard.service'
|
export * from './user-right-guard.service'
|
||||||
|
|
|
@ -0,0 +1,79 @@
|
||||||
|
import * as debug from 'debug'
|
||||||
|
import { Subject } from 'rxjs'
|
||||||
|
import { debounceTime, distinctUntilChanged } from 'rxjs/operators'
|
||||||
|
import { ActivatedRoute, Params, Router } from '@angular/router'
|
||||||
|
|
||||||
|
const logger = debug('peertube:tables:RouteFilter')
|
||||||
|
|
||||||
|
export abstract class RouteFilter {
|
||||||
|
search: string
|
||||||
|
|
||||||
|
protected searchStream: Subject<string>
|
||||||
|
|
||||||
|
protected route: ActivatedRoute
|
||||||
|
protected router: Router
|
||||||
|
|
||||||
|
initSearch () {
|
||||||
|
this.searchStream = new Subject()
|
||||||
|
|
||||||
|
this.searchStream
|
||||||
|
.pipe(
|
||||||
|
debounceTime(400),
|
||||||
|
distinctUntilChanged()
|
||||||
|
)
|
||||||
|
.subscribe(search => {
|
||||||
|
this.search = search
|
||||||
|
|
||||||
|
logger('On search %s.', this.search)
|
||||||
|
|
||||||
|
this.loadData()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
onSearch (event: Event) {
|
||||||
|
const target = event.target as HTMLInputElement
|
||||||
|
this.searchStream.next(target.value)
|
||||||
|
|
||||||
|
this.setQueryParams(target.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
resetTableFilter () {
|
||||||
|
this.setTableFilter('')
|
||||||
|
this.setQueryParams('')
|
||||||
|
this.resetSearch()
|
||||||
|
}
|
||||||
|
|
||||||
|
resetSearch () {
|
||||||
|
this.searchStream.next('')
|
||||||
|
this.setTableFilter('')
|
||||||
|
}
|
||||||
|
|
||||||
|
listenToSearchChange () {
|
||||||
|
this.route.queryParams
|
||||||
|
.subscribe(params => {
|
||||||
|
this.search = params.search || ''
|
||||||
|
|
||||||
|
// Primeng table will run an event to load data
|
||||||
|
this.setTableFilter(this.search)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
setTableFilter (filter: string, triggerEvent = true) {
|
||||||
|
// FIXME: cannot use ViewChild, so create a component for the filter input
|
||||||
|
const filterInput = document.getElementById('table-filter') as HTMLInputElement
|
||||||
|
if (!filterInput) return
|
||||||
|
|
||||||
|
filterInput.value = filter
|
||||||
|
|
||||||
|
if (triggerEvent) filterInput.dispatchEvent(new Event('keyup'))
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract loadData (): void
|
||||||
|
|
||||||
|
private setQueryParams (search: string) {
|
||||||
|
const queryParams: Params = {}
|
||||||
|
|
||||||
|
if (search) Object.assign(queryParams, { search })
|
||||||
|
this.router.navigate([ ], { queryParams })
|
||||||
|
}
|
||||||
|
}
|
|
@ -7,7 +7,7 @@
|
||||||
<span class="col-3 moderation-expanded-label" i18n>Reporter</span>
|
<span class="col-3 moderation-expanded-label" i18n>Reporter</span>
|
||||||
|
|
||||||
<span class="col-9 moderation-expanded-text">
|
<span class="col-9 moderation-expanded-text">
|
||||||
<a [routerLink]="[ baseRoute ]" [queryParams]="{ 'search': 'reporter:"' + abuse.reporterAccount.displayName + '"' }"
|
<a [routerLink]="[ '.' ]" [queryParams]="{ 'search': 'reporter:"' + abuse.reporterAccount.displayName + '"' }"
|
||||||
class="chip"
|
class="chip"
|
||||||
>
|
>
|
||||||
<my-actor-avatar [account]="abuse.reporterAccount"></my-actor-avatar>
|
<my-actor-avatar [account]="abuse.reporterAccount"></my-actor-avatar>
|
||||||
|
@ -16,7 +16,7 @@
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<a [routerLink]="[ baseRoute ]" [queryParams]="{ 'search': 'reporter:"' + abuse.reporterAccount.displayName + '"' }"
|
<a [routerLink]="[ '.' ]" [queryParams]="{ 'search': 'reporter:"' + abuse.reporterAccount.displayName + '"' }"
|
||||||
class="ml-auto text-muted abuse-details-links" i18n
|
class="ml-auto text-muted abuse-details-links" i18n
|
||||||
>
|
>
|
||||||
{abuse.countReportsForReporter, plural, =1 {1 report} other {{{ abuse.countReportsForReporter }} reports}}<span class="ml-1 glyphicon glyphicon-flag"></span>
|
{abuse.countReportsForReporter, plural, =1 {1 report} other {{{ abuse.countReportsForReporter }} reports}}<span class="ml-1 glyphicon glyphicon-flag"></span>
|
||||||
|
@ -27,7 +27,7 @@
|
||||||
<div class="d-flex" *ngIf="abuse.flaggedAccount">
|
<div class="d-flex" *ngIf="abuse.flaggedAccount">
|
||||||
<span class="col-3 moderation-expanded-label" i18n>Reportee</span>
|
<span class="col-3 moderation-expanded-label" i18n>Reportee</span>
|
||||||
<span class="col-9 moderation-expanded-text">
|
<span class="col-9 moderation-expanded-text">
|
||||||
<a [routerLink]="[ baseRoute ]" [queryParams]="{ 'search': 'reportee:"' +abuse.flaggedAccount.displayName + '"' }"
|
<a [routerLink]="[ '.' ]" [queryParams]="{ 'search': 'reportee:"' +abuse.flaggedAccount.displayName + '"' }"
|
||||||
class="chip"
|
class="chip"
|
||||||
>
|
>
|
||||||
<my-actor-avatar [account]="abuse.flaggedAccount"></my-actor-avatar>
|
<my-actor-avatar [account]="abuse.flaggedAccount"></my-actor-avatar>
|
||||||
|
@ -36,7 +36,7 @@
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<a *ngIf="isAdminView" [routerLink]="[ baseRoute ]" [queryParams]="{ 'search': 'reportee:"' +abuse.flaggedAccount.displayName + '"' }"
|
<a *ngIf="isAdminView" [routerLink]="[ '.' ]" [queryParams]="{ 'search': 'reportee:"' +abuse.flaggedAccount.displayName + '"' }"
|
||||||
class="ml-auto text-muted abuse-details-links" i18n
|
class="ml-auto text-muted abuse-details-links" i18n
|
||||||
>
|
>
|
||||||
{abuse.countReportsForReportee, plural, =1 {1 report} other {{{ abuse.countReportsForReportee }} reports}}<span class="ml-1 glyphicon glyphicon-flag"></span>
|
{abuse.countReportsForReportee, plural, =1 {1 report} other {{{ abuse.countReportsForReportee }} reports}}<span class="ml-1 glyphicon glyphicon-flag"></span>
|
||||||
|
@ -53,7 +53,7 @@
|
||||||
<div class="mt-3 d-flex">
|
<div class="mt-3 d-flex">
|
||||||
<span class="col-3 moderation-expanded-label">
|
<span class="col-3 moderation-expanded-label">
|
||||||
<ng-container i18n>Report</ng-container>
|
<ng-container i18n>Report</ng-container>
|
||||||
<a [routerLink]="[ baseRoute ]" [queryParams]="{ 'search': '#' + abuse.id }" class="ml-1 text-muted">#{{ abuse.id }}</a>
|
<a [routerLink]="[ '.' ]" [queryParams]="{ 'search': '#' + abuse.id }" class="ml-1 text-muted">#{{ abuse.id }}</a>
|
||||||
</span>
|
</span>
|
||||||
<span class="col-9 moderation-expanded-text" [innerHTML]="abuse.reasonHtml"></span>
|
<span class="col-9 moderation-expanded-text" [innerHTML]="abuse.reasonHtml"></span>
|
||||||
</div>
|
</div>
|
||||||
|
@ -61,7 +61,7 @@
|
||||||
<div *ngIf="getPredefinedReasons()" class="mt-2 d-flex">
|
<div *ngIf="getPredefinedReasons()" class="mt-2 d-flex">
|
||||||
<span class="col-3"></span>
|
<span class="col-3"></span>
|
||||||
<span class="col-9">
|
<span class="col-9">
|
||||||
<a *ngFor="let reason of getPredefinedReasons()" [routerLink]="[ baseRoute ]"
|
<a *ngFor="let reason of getPredefinedReasons()" [routerLink]="[ '.' ]"
|
||||||
[queryParams]="{ 'search': 'tag:' + reason.id }" class="chip rectangular bg-secondary text-light"
|
[queryParams]="{ 'search': 'tag:' + reason.id }" class="chip rectangular bg-secondary text-light"
|
||||||
>
|
>
|
||||||
<div>{{ reason.label }}</div>
|
<div>{{ reason.label }}</div>
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import { Component, Input } from '@angular/core'
|
import { Component, Input } from '@angular/core'
|
||||||
import { durationToString } from '@app/helpers'
|
import { durationToString } from '@app/helpers'
|
||||||
import { Account } from '@app/shared/shared-main'
|
|
||||||
import { AbusePredefinedReasonsString } from '@shared/models'
|
import { AbusePredefinedReasonsString } from '@shared/models'
|
||||||
import { ProcessedAbuse } from './processed-abuse.model'
|
import { ProcessedAbuse } from './processed-abuse.model'
|
||||||
|
|
||||||
|
@ -12,7 +11,6 @@ import { ProcessedAbuse } from './processed-abuse.model'
|
||||||
export class AbuseDetailsComponent {
|
export class AbuseDetailsComponent {
|
||||||
@Input() abuse: ProcessedAbuse
|
@Input() abuse: ProcessedAbuse
|
||||||
@Input() isAdminView: boolean
|
@Input() isAdminView: boolean
|
||||||
@Input() baseRoute: string
|
|
||||||
|
|
||||||
private predefinedReasonsTranslations: { [key in AbusePredefinedReasonsString]: string }
|
private predefinedReasonsTranslations: { [key in AbusePredefinedReasonsString]: string }
|
||||||
|
|
||||||
|
|
|
@ -8,28 +8,7 @@
|
||||||
<ng-template pTemplate="caption">
|
<ng-template pTemplate="caption">
|
||||||
<div class="caption">
|
<div class="caption">
|
||||||
<div class="ml-auto">
|
<div class="ml-auto">
|
||||||
<div class="input-group has-feedback has-clear">
|
<my-advanced-input-filter [filters]="inputFilters" (search)="onSearch($event)" (resetTableFilter)="resetTableFilter()"></my-advanced-input-filter>
|
||||||
<div class="input-group-prepend c-hand" ngbDropdown placement="bottom-left auto" container="body">
|
|
||||||
<div class="input-group-text" ngbDropdownToggle>
|
|
||||||
<span class="caret" aria-haspopup="menu" role="button"></span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div role="menu" ngbDropdownMenu>
|
|
||||||
<h6 class="dropdown-header" i18n>Advanced report filters</h6>
|
|
||||||
<a [routerLink]="[ baseRoute ]" [queryParams]="{ 'search': 'state:pending' }" class="dropdown-item" i18n>Unsolved reports</a>
|
|
||||||
<a [routerLink]="[ baseRoute ]" [queryParams]="{ 'search': 'state:accepted' }" class="dropdown-item" i18n>Accepted reports</a>
|
|
||||||
<a [routerLink]="[ baseRoute ]" [queryParams]="{ 'search': 'state:rejected' }" class="dropdown-item" i18n>Refused reports</a>
|
|
||||||
<a [routerLink]="[ baseRoute ]" [queryParams]="{ 'search': 'videoIs:blacklisted' }" class="dropdown-item" i18n>Reports with blocked videos</a>
|
|
||||||
<a [routerLink]="[ baseRoute ]" [queryParams]="{ 'search': 'videoIs:deleted' }" class="dropdown-item" i18n>Reports with deleted videos</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<input
|
|
||||||
type="text" name="table-filter" id="table-filter" i18n-placeholder placeholder="Filter..."
|
|
||||||
(keyup)="onSearch($event)"
|
|
||||||
>
|
|
||||||
<a class="glyphicon glyphicon-remove-sign form-control-feedback form-control-clear" (click)="resetTableFilter()"></a>
|
|
||||||
<span class="sr-only" i18n>Clear filters</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
@ -171,7 +150,7 @@
|
||||||
<ng-template pTemplate="rowexpansion" let-abuse>
|
<ng-template pTemplate="rowexpansion" let-abuse>
|
||||||
<tr>
|
<tr>
|
||||||
<td class="expand-cell" colspan="8">
|
<td class="expand-cell" colspan="8">
|
||||||
<my-abuse-details [abuse]="abuse" [baseRoute]="baseRoute" [isAdminView]="isAdminView()"></my-abuse-details>
|
<my-abuse-details [abuse]="abuse" [isAdminView]="isAdminView()"></my-abuse-details>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
|
|
@ -14,6 +14,7 @@ import { AbuseState, AdminAbuse } from '@shared/models'
|
||||||
import { AbuseMessageModalComponent } from './abuse-message-modal.component'
|
import { AbuseMessageModalComponent } from './abuse-message-modal.component'
|
||||||
import { ModerationCommentModalComponent } from './moderation-comment-modal.component'
|
import { ModerationCommentModalComponent } from './moderation-comment-modal.component'
|
||||||
import { ProcessedAbuse } from './processed-abuse.model'
|
import { ProcessedAbuse } from './processed-abuse.model'
|
||||||
|
import { AdvancedInputFilter } from '../shared-forms'
|
||||||
|
|
||||||
const logger = debug('peertube:moderation:AbuseListTableComponent')
|
const logger = debug('peertube:moderation:AbuseListTableComponent')
|
||||||
|
|
||||||
|
@ -24,7 +25,6 @@ const logger = debug('peertube:moderation:AbuseListTableComponent')
|
||||||
})
|
})
|
||||||
export class AbuseListTableComponent extends RestTable implements OnInit, AfterViewInit {
|
export class AbuseListTableComponent extends RestTable implements OnInit, AfterViewInit {
|
||||||
@Input() viewType: 'admin' | 'user'
|
@Input() viewType: 'admin' | 'user'
|
||||||
@Input() baseRoute: string
|
|
||||||
|
|
||||||
@ViewChild('abuseMessagesModal', { static: true }) abuseMessagesModal: AbuseMessageModalComponent
|
@ViewChild('abuseMessagesModal', { static: true }) abuseMessagesModal: AbuseMessageModalComponent
|
||||||
@ViewChild('moderationCommentModal', { static: true }) moderationCommentModal: ModerationCommentModalComponent
|
@ViewChild('moderationCommentModal', { static: true }) moderationCommentModal: ModerationCommentModalComponent
|
||||||
|
@ -36,6 +36,29 @@ export class AbuseListTableComponent extends RestTable implements OnInit, AfterV
|
||||||
|
|
||||||
abuseActions: DropdownAction<ProcessedAbuse>[][] = []
|
abuseActions: DropdownAction<ProcessedAbuse>[][] = []
|
||||||
|
|
||||||
|
inputFilters: AdvancedInputFilter[] = [
|
||||||
|
{
|
||||||
|
queryParams: { 'search': 'state:pending' },
|
||||||
|
label: $localize`Unsolved reports`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
queryParams: { 'search': 'state:accepted' },
|
||||||
|
label: $localize`Accepted reports`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
queryParams: { 'search': 'state:rejected' },
|
||||||
|
label: $localize`Refused reports`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
queryParams: { 'search': 'videoIs:blacklisted' },
|
||||||
|
label: $localize`Reports with blocked videos`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
queryParams: { 'search': 'videoIs:deleted' },
|
||||||
|
label: $localize`Reports with deleted videos`
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
constructor (
|
constructor (
|
||||||
protected route: ActivatedRoute,
|
protected route: ActivatedRoute,
|
||||||
protected router: Router,
|
protected router: Router,
|
||||||
|
|
|
@ -98,7 +98,7 @@ export class ActorAvatarComponent {
|
||||||
jkl: 'gray',
|
jkl: 'gray',
|
||||||
mno: 'yellow',
|
mno: 'yellow',
|
||||||
pqr: 'orange',
|
pqr: 'orange',
|
||||||
stv: 'red',
|
stvu: 'red',
|
||||||
wxyz: 'dark-blue'
|
wxyz: 'dark-blue'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
<div class="input-group has-feedback has-clear">
|
||||||
|
<div class="input-group-prepend c-hand" ngbDropdown placement="bottom-left auto" container="body">
|
||||||
|
<div class="input-group-text" ngbDropdownToggle>
|
||||||
|
<span class="caret" aria-haspopup="menu" role="button"></span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div role="menu" ngbDropdownMenu>
|
||||||
|
<h6 class="dropdown-header" i18n>Advanced filters</h6>
|
||||||
|
|
||||||
|
<a *ngFor="let filter of filters" [routerLink]="[ '.' ]" [queryParams]="filter.queryParams" class="dropdown-item">
|
||||||
|
{{ filter.label }}
|
||||||
|
</a>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<input
|
||||||
|
type="text" name="table-filter" id="table-filter" i18n-placeholder placeholder="Filter..."
|
||||||
|
(keyup)="onSearch($event)"
|
||||||
|
>
|
||||||
|
<a class="glyphicon glyphicon-remove-sign form-control-feedback form-control-clear" (click)="onResetTableFilter()"></a>
|
||||||
|
<span class="sr-only" i18n>Clear filters</span>
|
||||||
|
</div>
|
|
@ -0,0 +1,10 @@
|
||||||
|
@import '_variables';
|
||||||
|
@import '_mixins';
|
||||||
|
|
||||||
|
input {
|
||||||
|
@include peertube-input-text(250px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-group-text {
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
import { Component, EventEmitter, Input, Output } from '@angular/core'
|
||||||
|
import { Params } from '@angular/router'
|
||||||
|
|
||||||
|
export type AdvancedInputFilter = {
|
||||||
|
label: string
|
||||||
|
queryParams: Params
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'my-advanced-input-filter',
|
||||||
|
templateUrl: './advanced-input-filter.component.html',
|
||||||
|
styleUrls: [ './advanced-input-filter.component.scss' ]
|
||||||
|
})
|
||||||
|
export class AdvancedInputFilterComponent {
|
||||||
|
@Input() filters: AdvancedInputFilter[] = []
|
||||||
|
|
||||||
|
@Output() resetTableFilter = new EventEmitter<void>()
|
||||||
|
@Output() search = new EventEmitter<Event>()
|
||||||
|
|
||||||
|
onSearch (event: Event) {
|
||||||
|
this.search.emit(event)
|
||||||
|
}
|
||||||
|
|
||||||
|
onResetTableFilter () {
|
||||||
|
this.resetTableFilter.emit()
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,12 +1,14 @@
|
||||||
export * from './form-validator.service'
|
export * from './advanced-input-filter.component'
|
||||||
export * from './form-reactive'
|
export * from './form-reactive'
|
||||||
export * from './select'
|
export * from './form-validator.service'
|
||||||
export * from './input-toggle-hidden.component'
|
export * from './form-validator.service'
|
||||||
export * from './input-switch.component'
|
export * from './input-switch.component'
|
||||||
|
export * from './input-toggle-hidden.component'
|
||||||
export * from './markdown-textarea.component'
|
export * from './markdown-textarea.component'
|
||||||
export * from './peertube-checkbox.component'
|
export * from './peertube-checkbox.component'
|
||||||
export * from './preview-upload.component'
|
export * from './preview-upload.component'
|
||||||
export * from './reactive-file.component'
|
export * from './reactive-file.component'
|
||||||
|
export * from './select'
|
||||||
|
export * from './shared-form.module'
|
||||||
export * from './textarea-autoresize.directive'
|
export * from './textarea-autoresize.directive'
|
||||||
export * from './timestamp-input.component'
|
export * from './timestamp-input.component'
|
||||||
export * from './shared-form.module'
|
|
||||||
|
|
|
@ -5,6 +5,7 @@ import { FormsModule, ReactiveFormsModule } from '@angular/forms'
|
||||||
import { NgSelectModule } from '@ng-select/ng-select'
|
import { NgSelectModule } from '@ng-select/ng-select'
|
||||||
import { SharedGlobalIconModule } from '../shared-icons'
|
import { SharedGlobalIconModule } from '../shared-icons'
|
||||||
import { SharedMainModule } from '../shared-main/shared-main.module'
|
import { SharedMainModule } from '../shared-main/shared-main.module'
|
||||||
|
import { AdvancedInputFilterComponent } from './advanced-input-filter.component'
|
||||||
import { DynamicFormFieldComponent } from './dynamic-form-field.component'
|
import { DynamicFormFieldComponent } from './dynamic-form-field.component'
|
||||||
import { FormValidatorService } from './form-validator.service'
|
import { FormValidatorService } from './form-validator.service'
|
||||||
import { InputSwitchComponent } from './input-switch.component'
|
import { InputSwitchComponent } from './input-switch.component'
|
||||||
|
@ -52,7 +53,9 @@ import { TimestampInputComponent } from './timestamp-input.component'
|
||||||
SelectCheckboxComponent,
|
SelectCheckboxComponent,
|
||||||
SelectCustomValueComponent,
|
SelectCustomValueComponent,
|
||||||
|
|
||||||
DynamicFormFieldComponent
|
DynamicFormFieldComponent,
|
||||||
|
|
||||||
|
AdvancedInputFilterComponent
|
||||||
],
|
],
|
||||||
|
|
||||||
exports: [
|
exports: [
|
||||||
|
@ -78,7 +81,9 @@ import { TimestampInputComponent } from './timestamp-input.component'
|
||||||
SelectCheckboxComponent,
|
SelectCheckboxComponent,
|
||||||
SelectCustomValueComponent,
|
SelectCustomValueComponent,
|
||||||
|
|
||||||
DynamicFormFieldComponent
|
DynamicFormFieldComponent,
|
||||||
|
|
||||||
|
AdvancedInputFilterComponent
|
||||||
],
|
],
|
||||||
|
|
||||||
providers: [
|
providers: [
|
||||||
|
|
|
@ -124,7 +124,23 @@ export class VideoService implements VideosProvider {
|
||||||
|
|
||||||
let params = new HttpParams()
|
let params = new HttpParams()
|
||||||
params = this.restService.addRestGetParams(params, pagination, sort)
|
params = this.restService.addRestGetParams(params, pagination, sort)
|
||||||
params = this.restService.addObjectParams(params, { search })
|
|
||||||
|
if (search) {
|
||||||
|
const filters = this.restService.parseQueryStringFilter(search, {
|
||||||
|
isLive: {
|
||||||
|
prefix: 'isLive:',
|
||||||
|
isBoolean: true,
|
||||||
|
handler: v => {
|
||||||
|
if (v === 'true') return v
|
||||||
|
if (v === 'false') return v
|
||||||
|
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
params = this.restService.addObjectParams(params, filters)
|
||||||
|
}
|
||||||
|
|
||||||
return this.authHttp
|
return this.authHttp
|
||||||
.get<ResultList<Video>>(UserService.BASE_USERS_URL + 'me/videos', { params })
|
.get<ResultList<Video>>(UserService.BASE_USERS_URL + 'me/videos', { params })
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { NSFWQuery, SearchTargetType } from '@shared/models'
|
import { BooleanBothQuery, SearchTargetType } from '@shared/models'
|
||||||
|
|
||||||
export class AdvancedSearch {
|
export class AdvancedSearch {
|
||||||
startDate: string // ISO 8601
|
startDate: string // ISO 8601
|
||||||
|
@ -7,7 +7,7 @@ export class AdvancedSearch {
|
||||||
originallyPublishedStartDate: string // ISO 8601
|
originallyPublishedStartDate: string // ISO 8601
|
||||||
originallyPublishedEndDate: string // ISO 8601
|
originallyPublishedEndDate: string // ISO 8601
|
||||||
|
|
||||||
nsfw: NSFWQuery
|
nsfw: BooleanBothQuery
|
||||||
|
|
||||||
categoryOneOf: string
|
categoryOneOf: string
|
||||||
|
|
||||||
|
@ -33,7 +33,7 @@ export class AdvancedSearch {
|
||||||
endDate?: string
|
endDate?: string
|
||||||
originallyPublishedStartDate?: string
|
originallyPublishedStartDate?: string
|
||||||
originallyPublishedEndDate?: string
|
originallyPublishedEndDate?: string
|
||||||
nsfw?: NSFWQuery
|
nsfw?: BooleanBothQuery
|
||||||
categoryOneOf?: string
|
categoryOneOf?: string
|
||||||
licenceOneOf?: string
|
licenceOneOf?: string
|
||||||
languageOneOf?: string
|
languageOneOf?: string
|
||||||
|
|
|
@ -9,6 +9,10 @@ input[type=button] {
|
||||||
border-radius: inherit;
|
border-radius: inherit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
p-table .p-datatable-header .caption {
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
// Taken from old nova light theme
|
// Taken from old nova light theme
|
||||||
|
|
||||||
body .p-disabled {
|
body .p-disabled {
|
||||||
|
@ -512,10 +516,6 @@ p-table {
|
||||||
.left-buttons {
|
.left-buttons {
|
||||||
padding-left: 15px;
|
padding-left: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.input-group-text {
|
|
||||||
background-color: transparent;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
import * as express from 'express'
|
import * as express from 'express'
|
||||||
import { getServerActor } from '@server/models/application/application'
|
import { getServerActor } from '@server/models/application/application'
|
||||||
|
import { VideosWithSearchCommonQuery } from '@shared/models'
|
||||||
import { buildNSFWFilter, getCountVideos, isUserAbleToSearchRemoteURI } from '../../helpers/express-utils'
|
import { buildNSFWFilter, getCountVideos, isUserAbleToSearchRemoteURI } from '../../helpers/express-utils'
|
||||||
import { getFormattedObjects } from '../../helpers/utils'
|
import { getFormattedObjects } from '../../helpers/utils'
|
||||||
import { Hooks } from '../../lib/plugins/hooks'
|
|
||||||
import { JobQueue } from '../../lib/job-queue'
|
import { JobQueue } from '../../lib/job-queue'
|
||||||
|
import { Hooks } from '../../lib/plugins/hooks'
|
||||||
import {
|
import {
|
||||||
asyncMiddleware,
|
asyncMiddleware,
|
||||||
authenticate,
|
authenticate,
|
||||||
|
@ -158,25 +159,27 @@ async function listAccountVideos (req: express.Request, res: express.Response) {
|
||||||
const account = res.locals.account
|
const account = res.locals.account
|
||||||
const followerActorId = isUserAbleToSearchRemoteURI(res) ? null : undefined
|
const followerActorId = isUserAbleToSearchRemoteURI(res) ? null : undefined
|
||||||
const countVideos = getCountVideos(req)
|
const countVideos = getCountVideos(req)
|
||||||
|
const query = req.query as VideosWithSearchCommonQuery
|
||||||
|
|
||||||
const apiOptions = await Hooks.wrapObject({
|
const apiOptions = await Hooks.wrapObject({
|
||||||
followerActorId,
|
followerActorId,
|
||||||
start: req.query.start,
|
start: query.start,
|
||||||
count: req.query.count,
|
count: query.count,
|
||||||
sort: req.query.sort,
|
sort: query.sort,
|
||||||
includeLocalVideos: true,
|
includeLocalVideos: true,
|
||||||
categoryOneOf: req.query.categoryOneOf,
|
categoryOneOf: query.categoryOneOf,
|
||||||
licenceOneOf: req.query.licenceOneOf,
|
licenceOneOf: query.licenceOneOf,
|
||||||
languageOneOf: req.query.languageOneOf,
|
languageOneOf: query.languageOneOf,
|
||||||
tagsOneOf: req.query.tagsOneOf,
|
tagsOneOf: query.tagsOneOf,
|
||||||
tagsAllOf: req.query.tagsAllOf,
|
tagsAllOf: query.tagsAllOf,
|
||||||
filter: req.query.filter,
|
filter: query.filter,
|
||||||
nsfw: buildNSFWFilter(res, req.query.nsfw),
|
isLive: query.isLive,
|
||||||
|
nsfw: buildNSFWFilter(res, query.nsfw),
|
||||||
withFiles: false,
|
withFiles: false,
|
||||||
accountId: account.id,
|
accountId: account.id,
|
||||||
user: res.locals.oauth ? res.locals.oauth.token.User : undefined,
|
user: res.locals.oauth ? res.locals.oauth.token.User : undefined,
|
||||||
countVideos,
|
countVideos,
|
||||||
search: req.query.search
|
search: query.search
|
||||||
}, 'filter:api.accounts.videos.list.params')
|
}, 'filter:api.accounts.videos.list.params')
|
||||||
|
|
||||||
const resultList = await Hooks.wrapPromiseFun(
|
const resultList = await Hooks.wrapPromiseFun(
|
||||||
|
|
|
@ -111,7 +111,8 @@ async function getUserVideos (req: express.Request, res: express.Response) {
|
||||||
start: req.query.start,
|
start: req.query.start,
|
||||||
count: req.query.count,
|
count: req.query.count,
|
||||||
sort: req.query.sort,
|
sort: req.query.sort,
|
||||||
search: req.query.search
|
search: req.query.search,
|
||||||
|
isLive: req.query.isLive
|
||||||
}, 'filter:api.user.me.videos.list.params')
|
}, 'filter:api.user.me.videos.list.params')
|
||||||
|
|
||||||
const resultList = await Hooks.wrapPromiseFun(
|
const resultList = await Hooks.wrapPromiseFun(
|
||||||
|
|
|
@ -2,8 +2,8 @@ import 'multer'
|
||||||
import * as express from 'express'
|
import * as express from 'express'
|
||||||
import { sendUndoFollow } from '@server/lib/activitypub/send'
|
import { sendUndoFollow } from '@server/lib/activitypub/send'
|
||||||
import { VideoChannelModel } from '@server/models/video/video-channel'
|
import { VideoChannelModel } from '@server/models/video/video-channel'
|
||||||
|
import { VideosCommonQuery } from '@shared/models'
|
||||||
import { HttpStatusCode } from '../../../../shared/core-utils/miscs/http-error-codes'
|
import { HttpStatusCode } from '../../../../shared/core-utils/miscs/http-error-codes'
|
||||||
import { VideoFilter } from '../../../../shared/models/videos/video-query.type'
|
|
||||||
import { buildNSFWFilter, getCountVideos } from '../../../helpers/express-utils'
|
import { buildNSFWFilter, getCountVideos } from '../../../helpers/express-utils'
|
||||||
import { getFormattedObjects } from '../../../helpers/utils'
|
import { getFormattedObjects } from '../../../helpers/utils'
|
||||||
import { WEBSERVER } from '../../../initializers/constants'
|
import { WEBSERVER } from '../../../initializers/constants'
|
||||||
|
@ -170,19 +170,20 @@ async function getUserSubscriptions (req: express.Request, res: express.Response
|
||||||
async function getUserSubscriptionVideos (req: express.Request, res: express.Response) {
|
async function getUserSubscriptionVideos (req: express.Request, res: express.Response) {
|
||||||
const user = res.locals.oauth.token.User
|
const user = res.locals.oauth.token.User
|
||||||
const countVideos = getCountVideos(req)
|
const countVideos = getCountVideos(req)
|
||||||
|
const query = req.query as VideosCommonQuery
|
||||||
|
|
||||||
const resultList = await VideoModel.listForApi({
|
const resultList = await VideoModel.listForApi({
|
||||||
start: req.query.start,
|
start: query.start,
|
||||||
count: req.query.count,
|
count: query.count,
|
||||||
sort: req.query.sort,
|
sort: query.sort,
|
||||||
includeLocalVideos: false,
|
includeLocalVideos: false,
|
||||||
categoryOneOf: req.query.categoryOneOf,
|
categoryOneOf: query.categoryOneOf,
|
||||||
licenceOneOf: req.query.licenceOneOf,
|
licenceOneOf: query.licenceOneOf,
|
||||||
languageOneOf: req.query.languageOneOf,
|
languageOneOf: query.languageOneOf,
|
||||||
tagsOneOf: req.query.tagsOneOf,
|
tagsOneOf: query.tagsOneOf,
|
||||||
tagsAllOf: req.query.tagsAllOf,
|
tagsAllOf: query.tagsAllOf,
|
||||||
nsfw: buildNSFWFilter(res, req.query.nsfw),
|
nsfw: buildNSFWFilter(res, query.nsfw),
|
||||||
filter: req.query.filter as VideoFilter,
|
filter: query.filter,
|
||||||
withFiles: false,
|
withFiles: false,
|
||||||
followerActorId: user.Account.Actor.id,
|
followerActorId: user.Account.Actor.id,
|
||||||
user,
|
user,
|
||||||
|
|
|
@ -2,7 +2,7 @@ import * as express from 'express'
|
||||||
import { Hooks } from '@server/lib/plugins/hooks'
|
import { Hooks } from '@server/lib/plugins/hooks'
|
||||||
import { getServerActor } from '@server/models/application/application'
|
import { getServerActor } from '@server/models/application/application'
|
||||||
import { MChannelBannerAccountDefault } from '@server/types/models'
|
import { MChannelBannerAccountDefault } from '@server/types/models'
|
||||||
import { ActorImageType, VideoChannelCreate, VideoChannelUpdate } from '../../../shared'
|
import { ActorImageType, VideoChannelCreate, VideoChannelUpdate, VideosCommonQuery } from '../../../shared'
|
||||||
import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes'
|
import { HttpStatusCode } from '../../../shared/core-utils/miscs/http-error-codes'
|
||||||
import { auditLoggerFactory, getAuditIdFromRes, VideoChannelAuditView } from '../../helpers/audit-logger'
|
import { auditLoggerFactory, getAuditIdFromRes, VideoChannelAuditView } from '../../helpers/audit-logger'
|
||||||
import { resetSequelizeInstance } from '../../helpers/database-utils'
|
import { resetSequelizeInstance } from '../../helpers/database-utils'
|
||||||
|
@ -312,20 +312,21 @@ async function listVideoChannelVideos (req: express.Request, res: express.Respon
|
||||||
const videoChannelInstance = res.locals.videoChannel
|
const videoChannelInstance = res.locals.videoChannel
|
||||||
const followerActorId = isUserAbleToSearchRemoteURI(res) ? null : undefined
|
const followerActorId = isUserAbleToSearchRemoteURI(res) ? null : undefined
|
||||||
const countVideos = getCountVideos(req)
|
const countVideos = getCountVideos(req)
|
||||||
|
const query = req.query as VideosCommonQuery
|
||||||
|
|
||||||
const apiOptions = await Hooks.wrapObject({
|
const apiOptions = await Hooks.wrapObject({
|
||||||
followerActorId,
|
followerActorId,
|
||||||
start: req.query.start,
|
start: query.start,
|
||||||
count: req.query.count,
|
count: query.count,
|
||||||
sort: req.query.sort,
|
sort: query.sort,
|
||||||
includeLocalVideos: true,
|
includeLocalVideos: true,
|
||||||
categoryOneOf: req.query.categoryOneOf,
|
categoryOneOf: query.categoryOneOf,
|
||||||
licenceOneOf: req.query.licenceOneOf,
|
licenceOneOf: query.licenceOneOf,
|
||||||
languageOneOf: req.query.languageOneOf,
|
languageOneOf: query.languageOneOf,
|
||||||
tagsOneOf: req.query.tagsOneOf,
|
tagsOneOf: query.tagsOneOf,
|
||||||
tagsAllOf: req.query.tagsAllOf,
|
tagsAllOf: query.tagsAllOf,
|
||||||
filter: req.query.filter,
|
filter: query.filter,
|
||||||
nsfw: buildNSFWFilter(res, req.query.nsfw),
|
nsfw: buildNSFWFilter(res, query.nsfw),
|
||||||
withFiles: false,
|
withFiles: false,
|
||||||
videoChannelId: videoChannelInstance.id,
|
videoChannelId: videoChannelInstance.id,
|
||||||
user: res.locals.oauth ? res.locals.oauth.token.User : undefined,
|
user: res.locals.oauth ? res.locals.oauth.token.User : undefined,
|
||||||
|
|
|
@ -10,9 +10,8 @@ import { addOptimizeOrMergeAudioJob, buildLocalVideoFromReq, buildVideoThumbnail
|
||||||
import { generateVideoFilename, getVideoFilePath } from '@server/lib/video-paths'
|
import { generateVideoFilename, getVideoFilePath } from '@server/lib/video-paths'
|
||||||
import { getServerActor } from '@server/models/application/application'
|
import { getServerActor } from '@server/models/application/application'
|
||||||
import { MVideo, MVideoFile, MVideoFullLight } from '@server/types/models'
|
import { MVideo, MVideoFile, MVideoFullLight } from '@server/types/models'
|
||||||
import { VideoCreate, VideoState, VideoUpdate } from '../../../../shared'
|
import { VideoCreate, VideosCommonQuery, VideoState, VideoUpdate } from '../../../../shared'
|
||||||
import { HttpStatusCode } from '../../../../shared/core-utils/miscs/http-error-codes'
|
import { HttpStatusCode } from '../../../../shared/core-utils/miscs/http-error-codes'
|
||||||
import { VideoFilter } from '../../../../shared/models/videos/video-query.type'
|
|
||||||
import { auditLoggerFactory, getAuditIdFromRes, VideoAuditView } from '../../../helpers/audit-logger'
|
import { auditLoggerFactory, getAuditIdFromRes, VideoAuditView } from '../../../helpers/audit-logger'
|
||||||
import { resetSequelizeInstance, retryTransactionWrapper } from '../../../helpers/database-utils'
|
import { resetSequelizeInstance, retryTransactionWrapper } from '../../../helpers/database-utils'
|
||||||
import { buildNSFWFilter, createReqFiles, getCountVideos } from '../../../helpers/express-utils'
|
import { buildNSFWFilter, createReqFiles, getCountVideos } from '../../../helpers/express-utils'
|
||||||
|
@ -494,20 +493,22 @@ async function getVideoFileMetadata (req: express.Request, res: express.Response
|
||||||
}
|
}
|
||||||
|
|
||||||
async function listVideos (req: express.Request, res: express.Response) {
|
async function listVideos (req: express.Request, res: express.Response) {
|
||||||
|
const query = req.query as VideosCommonQuery
|
||||||
const countVideos = getCountVideos(req)
|
const countVideos = getCountVideos(req)
|
||||||
|
|
||||||
const apiOptions = await Hooks.wrapObject({
|
const apiOptions = await Hooks.wrapObject({
|
||||||
start: req.query.start,
|
start: query.start,
|
||||||
count: req.query.count,
|
count: query.count,
|
||||||
sort: req.query.sort,
|
sort: query.sort,
|
||||||
includeLocalVideos: true,
|
includeLocalVideos: true,
|
||||||
categoryOneOf: req.query.categoryOneOf,
|
categoryOneOf: query.categoryOneOf,
|
||||||
licenceOneOf: req.query.licenceOneOf,
|
licenceOneOf: query.licenceOneOf,
|
||||||
languageOneOf: req.query.languageOneOf,
|
languageOneOf: query.languageOneOf,
|
||||||
tagsOneOf: req.query.tagsOneOf,
|
tagsOneOf: query.tagsOneOf,
|
||||||
tagsAllOf: req.query.tagsAllOf,
|
tagsAllOf: query.tagsAllOf,
|
||||||
nsfw: buildNSFWFilter(res, req.query.nsfw),
|
nsfw: buildNSFWFilter(res, query.nsfw),
|
||||||
filter: req.query.filter as VideoFilter,
|
isLive: query.isLive,
|
||||||
|
filter: query.filter,
|
||||||
withFiles: false,
|
withFiles: false,
|
||||||
user: res.locals.oauth ? res.locals.oauth.token.User : undefined,
|
user: res.locals.oauth ? res.locals.oauth.token.User : undefined,
|
||||||
countVideos
|
countVideos
|
||||||
|
|
|
@ -11,7 +11,7 @@ function isStringArray (value: any) {
|
||||||
return isArray(value) && value.every(v => typeof v === 'string')
|
return isArray(value) && value.every(v => typeof v === 'string')
|
||||||
}
|
}
|
||||||
|
|
||||||
function isNSFWQueryValid (value: any) {
|
function isBooleanBothQueryValid (value: any) {
|
||||||
return value === 'true' || value === 'false' || value === 'both'
|
return value === 'true' || value === 'false' || value === 'both'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -32,6 +32,6 @@ function isSearchTargetValid (value: SearchTargetType) {
|
||||||
export {
|
export {
|
||||||
isNumberArray,
|
isNumberArray,
|
||||||
isStringArray,
|
isStringArray,
|
||||||
isNSFWQueryValid,
|
isBooleanBothQueryValid,
|
||||||
isSearchTargetValid
|
isSearchTargetValid
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,7 +20,7 @@ import {
|
||||||
toIntOrNull,
|
toIntOrNull,
|
||||||
toValueOrNull
|
toValueOrNull
|
||||||
} from '../../../helpers/custom-validators/misc'
|
} from '../../../helpers/custom-validators/misc'
|
||||||
import { isNSFWQueryValid, isNumberArray, isStringArray } from '../../../helpers/custom-validators/search'
|
import { isBooleanBothQueryValid, isNumberArray, isStringArray } from '../../../helpers/custom-validators/search'
|
||||||
import { checkUserCanTerminateOwnershipChange, doesChangeVideoOwnershipExist } from '../../../helpers/custom-validators/video-ownership'
|
import { checkUserCanTerminateOwnershipChange, doesChangeVideoOwnershipExist } from '../../../helpers/custom-validators/video-ownership'
|
||||||
import {
|
import {
|
||||||
isScheduleVideoUpdatePrivacyValid,
|
isScheduleVideoUpdatePrivacyValid,
|
||||||
|
@ -439,7 +439,11 @@ const commonVideosFiltersValidator = [
|
||||||
.custom(isStringArray).withMessage('Should have a valid all of tags array'),
|
.custom(isStringArray).withMessage('Should have a valid all of tags array'),
|
||||||
query('nsfw')
|
query('nsfw')
|
||||||
.optional()
|
.optional()
|
||||||
.custom(isNSFWQueryValid).withMessage('Should have a valid NSFW attribute'),
|
.custom(isBooleanBothQueryValid).withMessage('Should have a valid NSFW attribute'),
|
||||||
|
query('isLive')
|
||||||
|
.optional()
|
||||||
|
.customSanitizer(toBooleanOrNull)
|
||||||
|
.custom(isBooleanValid).withMessage('Should have a valid live boolean'),
|
||||||
query('filter')
|
query('filter')
|
||||||
.optional()
|
.optional()
|
||||||
.custom(isVideoFilterValid).withMessage('Should have a valid filter attribute'),
|
.custom(isVideoFilterValid).withMessage('Should have a valid filter attribute'),
|
||||||
|
|
|
@ -16,9 +16,11 @@ export type BuildVideosQueryOptions = {
|
||||||
start: number
|
start: number
|
||||||
sort: string
|
sort: string
|
||||||
|
|
||||||
filter?: VideoFilter
|
|
||||||
categoryOneOf?: number[]
|
|
||||||
nsfw?: boolean
|
nsfw?: boolean
|
||||||
|
filter?: VideoFilter
|
||||||
|
isLive?: boolean
|
||||||
|
|
||||||
|
categoryOneOf?: number[]
|
||||||
licenceOneOf?: number[]
|
licenceOneOf?: number[]
|
||||||
languageOneOf?: string[]
|
languageOneOf?: string[]
|
||||||
tagsOneOf?: string[]
|
tagsOneOf?: string[]
|
||||||
|
@ -199,10 +201,14 @@ function buildListQuery (model: typeof Model, options: BuildVideosQueryOptions)
|
||||||
|
|
||||||
if (options.nsfw === true) {
|
if (options.nsfw === true) {
|
||||||
and.push('"video"."nsfw" IS TRUE')
|
and.push('"video"."nsfw" IS TRUE')
|
||||||
|
} else if (options.nsfw === false) {
|
||||||
|
and.push('"video"."nsfw" IS FALSE')
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options.nsfw === false) {
|
if (options.isLive === true) {
|
||||||
and.push('"video"."nsfw" IS FALSE')
|
and.push('"video"."isLive" IS TRUE')
|
||||||
|
} else if (options.isLive === false) {
|
||||||
|
and.push('"video"."isLive" IS FALSE')
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options.categoryOneOf) {
|
if (options.categoryOneOf) {
|
||||||
|
|
|
@ -1021,14 +1021,28 @@ export class VideoModel extends Model {
|
||||||
start: number
|
start: number
|
||||||
count: number
|
count: number
|
||||||
sort: string
|
sort: string
|
||||||
|
isLive?: boolean
|
||||||
search?: string
|
search?: string
|
||||||
}) {
|
}) {
|
||||||
const { accountId, start, count, sort, search } = options
|
const { accountId, start, count, sort, search, isLive } = options
|
||||||
|
|
||||||
function buildBaseQuery (): FindOptions {
|
function buildBaseQuery (): FindOptions {
|
||||||
let baseQuery = {
|
const where: WhereOptions = {}
|
||||||
|
|
||||||
|
if (search) {
|
||||||
|
where.name = {
|
||||||
|
[Op.iLike]: '%' + search + '%'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isLive) {
|
||||||
|
where.isLive = isLive
|
||||||
|
}
|
||||||
|
|
||||||
|
const baseQuery = {
|
||||||
offset: start,
|
offset: start,
|
||||||
limit: count,
|
limit: count,
|
||||||
|
where,
|
||||||
order: getVideoSort(sort),
|
order: getVideoSort(sort),
|
||||||
include: [
|
include: [
|
||||||
{
|
{
|
||||||
|
@ -1047,16 +1061,6 @@ export class VideoModel extends Model {
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
if (search) {
|
|
||||||
baseQuery = Object.assign(baseQuery, {
|
|
||||||
where: {
|
|
||||||
name: {
|
|
||||||
[Op.iLike]: '%' + search + '%'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return baseQuery
|
return baseQuery
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1084,23 +1088,34 @@ export class VideoModel extends Model {
|
||||||
start: number
|
start: number
|
||||||
count: number
|
count: number
|
||||||
sort: string
|
sort: string
|
||||||
|
|
||||||
nsfw: boolean
|
nsfw: boolean
|
||||||
|
filter?: VideoFilter
|
||||||
|
isLive?: boolean
|
||||||
|
|
||||||
includeLocalVideos: boolean
|
includeLocalVideos: boolean
|
||||||
withFiles: boolean
|
withFiles: boolean
|
||||||
|
|
||||||
categoryOneOf?: number[]
|
categoryOneOf?: number[]
|
||||||
licenceOneOf?: number[]
|
licenceOneOf?: number[]
|
||||||
languageOneOf?: string[]
|
languageOneOf?: string[]
|
||||||
tagsOneOf?: string[]
|
tagsOneOf?: string[]
|
||||||
tagsAllOf?: string[]
|
tagsAllOf?: string[]
|
||||||
filter?: VideoFilter
|
|
||||||
accountId?: number
|
accountId?: number
|
||||||
videoChannelId?: number
|
videoChannelId?: number
|
||||||
|
|
||||||
followerActorId?: number
|
followerActorId?: number
|
||||||
|
|
||||||
videoPlaylistId?: number
|
videoPlaylistId?: number
|
||||||
|
|
||||||
trendingDays?: number
|
trendingDays?: number
|
||||||
|
|
||||||
user?: MUserAccountId
|
user?: MUserAccountId
|
||||||
historyOfUser?: MUserId
|
historyOfUser?: MUserId
|
||||||
|
|
||||||
countVideos?: boolean
|
countVideos?: boolean
|
||||||
|
|
||||||
search?: string
|
search?: string
|
||||||
}) {
|
}) {
|
||||||
if ((options.filter === 'all-local' || options.filter === 'all') && !options.user.hasRight(UserRight.SEE_ALL_VIDEOS)) {
|
if ((options.filter === 'all-local' || options.filter === 'all') && !options.user.hasRight(UserRight.SEE_ALL_VIDEOS)) {
|
||||||
|
@ -1128,6 +1143,7 @@ export class VideoModel extends Model {
|
||||||
followerActorId,
|
followerActorId,
|
||||||
serverAccountId: serverActor.Account.id,
|
serverAccountId: serverActor.Account.id,
|
||||||
nsfw: options.nsfw,
|
nsfw: options.nsfw,
|
||||||
|
isLive: options.isLive,
|
||||||
categoryOneOf: options.categoryOneOf,
|
categoryOneOf: options.categoryOneOf,
|
||||||
licenceOneOf: options.licenceOneOf,
|
licenceOneOf: options.licenceOneOf,
|
||||||
languageOneOf: options.languageOneOf,
|
languageOneOf: options.languageOneOf,
|
||||||
|
@ -1160,6 +1176,7 @@ export class VideoModel extends Model {
|
||||||
originallyPublishedStartDate?: string
|
originallyPublishedStartDate?: string
|
||||||
originallyPublishedEndDate?: string
|
originallyPublishedEndDate?: string
|
||||||
nsfw?: boolean
|
nsfw?: boolean
|
||||||
|
isLive?: boolean
|
||||||
categoryOneOf?: number[]
|
categoryOneOf?: number[]
|
||||||
licenceOneOf?: number[]
|
licenceOneOf?: number[]
|
||||||
languageOneOf?: string[]
|
languageOneOf?: string[]
|
||||||
|
@ -1171,23 +1188,32 @@ export class VideoModel extends Model {
|
||||||
filter?: VideoFilter
|
filter?: VideoFilter
|
||||||
}) {
|
}) {
|
||||||
const serverActor = await getServerActor()
|
const serverActor = await getServerActor()
|
||||||
|
|
||||||
const queryOptions = {
|
const queryOptions = {
|
||||||
followerActorId: serverActor.id,
|
followerActorId: serverActor.id,
|
||||||
serverAccountId: serverActor.Account.id,
|
serverAccountId: serverActor.Account.id,
|
||||||
|
|
||||||
includeLocalVideos: options.includeLocalVideos,
|
includeLocalVideos: options.includeLocalVideos,
|
||||||
nsfw: options.nsfw,
|
nsfw: options.nsfw,
|
||||||
|
isLive: options.isLive,
|
||||||
|
|
||||||
categoryOneOf: options.categoryOneOf,
|
categoryOneOf: options.categoryOneOf,
|
||||||
licenceOneOf: options.licenceOneOf,
|
licenceOneOf: options.licenceOneOf,
|
||||||
languageOneOf: options.languageOneOf,
|
languageOneOf: options.languageOneOf,
|
||||||
|
|
||||||
tagsOneOf: options.tagsOneOf,
|
tagsOneOf: options.tagsOneOf,
|
||||||
tagsAllOf: options.tagsAllOf,
|
tagsAllOf: options.tagsAllOf,
|
||||||
|
|
||||||
user: options.user,
|
user: options.user,
|
||||||
filter: options.filter,
|
filter: options.filter,
|
||||||
|
|
||||||
start: options.start,
|
start: options.start,
|
||||||
count: options.count,
|
count: options.count,
|
||||||
sort: options.sort,
|
sort: options.sort,
|
||||||
|
|
||||||
startDate: options.startDate,
|
startDate: options.startDate,
|
||||||
endDate: options.endDate,
|
endDate: options.endDate,
|
||||||
|
|
||||||
originallyPublishedStartDate: options.originallyPublishedStartDate,
|
originallyPublishedStartDate: options.originallyPublishedStartDate,
|
||||||
originallyPublishedEndDate: options.originallyPublishedEndDate,
|
originallyPublishedEndDate: options.originallyPublishedEndDate,
|
||||||
|
|
||||||
|
|
|
@ -19,10 +19,12 @@ import {
|
||||||
doubleFollow,
|
doubleFollow,
|
||||||
flushAndRunMultipleServers,
|
flushAndRunMultipleServers,
|
||||||
getLive,
|
getLive,
|
||||||
|
getMyVideosWithFilter,
|
||||||
getPlaylist,
|
getPlaylist,
|
||||||
getVideo,
|
getVideo,
|
||||||
getVideoIdFromUUID,
|
getVideoIdFromUUID,
|
||||||
getVideosList,
|
getVideosList,
|
||||||
|
getVideosWithFilters,
|
||||||
killallServers,
|
killallServers,
|
||||||
makeRawRequest,
|
makeRawRequest,
|
||||||
removeVideo,
|
removeVideo,
|
||||||
|
@ -37,6 +39,7 @@ import {
|
||||||
testImage,
|
testImage,
|
||||||
updateCustomSubConfig,
|
updateCustomSubConfig,
|
||||||
updateLive,
|
updateLive,
|
||||||
|
uploadVideoAndGetId,
|
||||||
viewVideo,
|
viewVideo,
|
||||||
wait,
|
wait,
|
||||||
waitJobs,
|
waitJobs,
|
||||||
|
@ -229,6 +232,68 @@ describe('Test live', function () {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('Live filters', function () {
|
||||||
|
let command: any
|
||||||
|
let liveVideoId: string
|
||||||
|
let vodVideoId: string
|
||||||
|
|
||||||
|
before(async function () {
|
||||||
|
this.timeout(120000)
|
||||||
|
|
||||||
|
vodVideoId = (await uploadVideoAndGetId({ server: servers[0], videoName: 'vod video' })).uuid
|
||||||
|
|
||||||
|
const liveOptions = { name: 'live', privacy: VideoPrivacy.PUBLIC, channelId: servers[0].videoChannel.id }
|
||||||
|
const resLive = await createLive(servers[0].url, servers[0].accessToken, liveOptions)
|
||||||
|
liveVideoId = resLive.body.video.uuid
|
||||||
|
|
||||||
|
command = await sendRTMPStreamInVideo(servers[0].url, servers[0].accessToken, liveVideoId)
|
||||||
|
await waitUntilLivePublishedOnAllServers(liveVideoId)
|
||||||
|
await waitJobs(servers)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should only display lives', async function () {
|
||||||
|
const res = await getVideosWithFilters(servers[0].url, { isLive: true })
|
||||||
|
|
||||||
|
expect(res.body.total).to.equal(1)
|
||||||
|
expect(res.body.data).to.have.lengthOf(1)
|
||||||
|
expect(res.body.data[0].name).to.equal('live')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should not display lives', async function () {
|
||||||
|
const res = await getVideosWithFilters(servers[0].url, { isLive: false })
|
||||||
|
|
||||||
|
expect(res.body.total).to.equal(1)
|
||||||
|
expect(res.body.data).to.have.lengthOf(1)
|
||||||
|
expect(res.body.data[0].name).to.equal('vod video')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should display my lives', async function () {
|
||||||
|
this.timeout(60000)
|
||||||
|
|
||||||
|
await stopFfmpeg(command)
|
||||||
|
await waitJobs(servers)
|
||||||
|
|
||||||
|
const res = await getMyVideosWithFilter(servers[0].url, servers[0].accessToken, { isLive: true })
|
||||||
|
const videos = res.body.data as Video[]
|
||||||
|
|
||||||
|
const result = videos.every(v => v.isLive)
|
||||||
|
expect(result).to.be.true
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should not display my lives', async function () {
|
||||||
|
const res = await getMyVideosWithFilter(servers[0].url, servers[0].accessToken, { isLive: false })
|
||||||
|
const videos = res.body.data as Video[]
|
||||||
|
|
||||||
|
const result = videos.every(v => !v.isLive)
|
||||||
|
expect(result).to.be.true
|
||||||
|
})
|
||||||
|
|
||||||
|
after(async function () {
|
||||||
|
await removeVideo(servers[0].url, servers[0].accessToken, vodVideoId)
|
||||||
|
await removeVideo(servers[0].url, servers[0].accessToken, liveVideoId)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
describe('Stream checks', function () {
|
describe('Stream checks', function () {
|
||||||
let liveVideo: LiveVideo & VideoDetails
|
let liveVideo: LiveVideo & VideoDetails
|
||||||
let rtmpUrl: string
|
let rtmpUrl: string
|
||||||
|
|
|
@ -1,17 +1,24 @@
|
||||||
/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
|
/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
|
||||||
|
|
||||||
import * as chai from 'chai'
|
|
||||||
import 'mocha'
|
import 'mocha'
|
||||||
|
import * as chai from 'chai'
|
||||||
|
import { VideoPrivacy } from '@shared/models'
|
||||||
import {
|
import {
|
||||||
advancedVideosSearch,
|
advancedVideosSearch,
|
||||||
cleanupTests,
|
cleanupTests,
|
||||||
|
createLive,
|
||||||
flushAndRunServer,
|
flushAndRunServer,
|
||||||
immutableAssign,
|
immutableAssign,
|
||||||
searchVideo,
|
searchVideo,
|
||||||
|
sendRTMPStreamInVideo,
|
||||||
ServerInfo,
|
ServerInfo,
|
||||||
setAccessTokensToServers,
|
setAccessTokensToServers,
|
||||||
|
setDefaultVideoChannel,
|
||||||
|
stopFfmpeg,
|
||||||
|
updateCustomSubConfig,
|
||||||
uploadVideo,
|
uploadVideo,
|
||||||
wait
|
wait,
|
||||||
|
waitUntilLivePublished
|
||||||
} from '../../../../shared/extra-utils'
|
} from '../../../../shared/extra-utils'
|
||||||
import { createVideoCaption } from '../../../../shared/extra-utils/videos/video-captions'
|
import { createVideoCaption } from '../../../../shared/extra-utils/videos/video-captions'
|
||||||
|
|
||||||
|
@ -28,6 +35,7 @@ describe('Test videos search', function () {
|
||||||
server = await flushAndRunServer(1)
|
server = await flushAndRunServer(1)
|
||||||
|
|
||||||
await setAccessTokensToServers([ server ])
|
await setAccessTokensToServers([ server ])
|
||||||
|
await setDefaultVideoChannel([ server ])
|
||||||
|
|
||||||
{
|
{
|
||||||
const attributes1 = {
|
const attributes1 = {
|
||||||
|
@ -449,6 +457,43 @@ describe('Test videos search', function () {
|
||||||
expect(res.body.data[0].name).to.equal('1111 2222 3333 - 3')
|
expect(res.body.data[0].name).to.equal('1111 2222 3333 - 3')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('Should search by live', async function () {
|
||||||
|
this.timeout(30000)
|
||||||
|
|
||||||
|
{
|
||||||
|
const options = {
|
||||||
|
search: {
|
||||||
|
searchIndex: { enabled: false }
|
||||||
|
},
|
||||||
|
live: { enabled: true }
|
||||||
|
}
|
||||||
|
await updateCustomSubConfig(server.url, server.accessToken, options)
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const res = await advancedVideosSearch(server.url, { isLive: true })
|
||||||
|
|
||||||
|
expect(res.body.total).to.equal(0)
|
||||||
|
expect(res.body.data).to.have.lengthOf(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const liveOptions = { name: 'live', privacy: VideoPrivacy.PUBLIC, channelId: server.videoChannel.id }
|
||||||
|
const resLive = await createLive(server.url, server.accessToken, liveOptions)
|
||||||
|
const liveVideoId = resLive.body.video.uuid
|
||||||
|
|
||||||
|
const command = await sendRTMPStreamInVideo(server.url, server.accessToken, liveVideoId)
|
||||||
|
await waitUntilLivePublished(server.url, server.accessToken, liveVideoId)
|
||||||
|
|
||||||
|
const res = await advancedVideosSearch(server.url, { isLive: true })
|
||||||
|
|
||||||
|
expect(res.body.total).to.equal(1)
|
||||||
|
expect(res.body.data[0].name).to.equal('live')
|
||||||
|
|
||||||
|
await stopFfmpeg(command)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
after(async function () {
|
after(async function () {
|
||||||
await cleanupTests([ server ])
|
await cleanupTests([ server ])
|
||||||
})
|
})
|
||||||
|
|
|
@ -387,11 +387,11 @@ describe('Test a single server', function () {
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Should filter by tags and category', async function () {
|
it('Should filter by tags and category', async function () {
|
||||||
const res1 = await getVideosWithFilters(server.url, { tagsAllOf: [ 'tagup1', 'tagup2' ], categoryOneOf: 4 })
|
const res1 = await getVideosWithFilters(server.url, { tagsAllOf: [ 'tagup1', 'tagup2' ], categoryOneOf: [ 4 ] })
|
||||||
expect(res1.body.total).to.equal(1)
|
expect(res1.body.total).to.equal(1)
|
||||||
expect(res1.body.data[0].name).to.equal('my super video updated')
|
expect(res1.body.data[0].name).to.equal('my super video updated')
|
||||||
|
|
||||||
const res2 = await getVideosWithFilters(server.url, { tagsAllOf: [ 'tagup1', 'tagup2' ], categoryOneOf: 3 })
|
const res2 = await getVideosWithFilters(server.url, { tagsAllOf: [ 'tagup1', 'tagup2' ], categoryOneOf: [ 3 ] })
|
||||||
expect(res2.body.total).to.equal(0)
|
expect(res2.body.total).to.equal(0)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,7 @@ import * as request from 'supertest'
|
||||||
import { v4 as uuidv4 } from 'uuid'
|
import { v4 as uuidv4 } from 'uuid'
|
||||||
import validator from 'validator'
|
import validator from 'validator'
|
||||||
import { HttpStatusCode } from '@shared/core-utils'
|
import { HttpStatusCode } from '@shared/core-utils'
|
||||||
|
import { VideosCommonQuery } from '@shared/models'
|
||||||
import { loadLanguages, VIDEO_CATEGORIES, VIDEO_LANGUAGES, VIDEO_LICENCES, VIDEO_PRIVACIES } from '../../../server/initializers/constants'
|
import { loadLanguages, VIDEO_CATEGORIES, VIDEO_LANGUAGES, VIDEO_LICENCES, VIDEO_PRIVACIES } from '../../../server/initializers/constants'
|
||||||
import { VideoDetails, VideoPrivacy } from '../../models/videos'
|
import { VideoDetails, VideoPrivacy } from '../../models/videos'
|
||||||
import {
|
import {
|
||||||
|
@ -195,6 +196,18 @@ function getMyVideos (url: string, accessToken: string, start: number, count: nu
|
||||||
.expect('Content-Type', /json/)
|
.expect('Content-Type', /json/)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getMyVideosWithFilter (url: string, accessToken: string, query: { isLive?: boolean }) {
|
||||||
|
const path = '/api/v1/users/me/videos'
|
||||||
|
|
||||||
|
return makeGetRequest({
|
||||||
|
url,
|
||||||
|
path,
|
||||||
|
token: accessToken,
|
||||||
|
query,
|
||||||
|
statusCodeExpected: HttpStatusCode.OK_200
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
function getAccountVideos (
|
function getAccountVideos (
|
||||||
url: string,
|
url: string,
|
||||||
accessToken: string,
|
accessToken: string,
|
||||||
|
@ -295,7 +308,7 @@ function getVideosListSort (url: string, sort: string) {
|
||||||
.expect('Content-Type', /json/)
|
.expect('Content-Type', /json/)
|
||||||
}
|
}
|
||||||
|
|
||||||
function getVideosWithFilters (url: string, query: { tagsAllOf: string[], categoryOneOf: number[] | number }) {
|
function getVideosWithFilters (url: string, query: VideosCommonQuery) {
|
||||||
const path = '/api/v1/videos'
|
const path = '/api/v1/videos'
|
||||||
|
|
||||||
return request(url)
|
return request(url)
|
||||||
|
@ -751,6 +764,7 @@ export {
|
||||||
completeVideoCheck,
|
completeVideoCheck,
|
||||||
checkVideoFilesWereRemoved,
|
checkVideoFilesWereRemoved,
|
||||||
getPlaylistVideos,
|
getPlaylistVideos,
|
||||||
|
getMyVideosWithFilter,
|
||||||
uploadVideoAndGetId,
|
uploadVideoAndGetId,
|
||||||
getLocalIdByUUID,
|
getLocalIdByUUID,
|
||||||
getVideoIdFromUUID
|
getVideoIdFromUUID
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
export type BooleanBothQuery = 'true' | 'false' | 'both'
|
|
@ -1,4 +1,5 @@
|
||||||
export * from './nsfw-query.model'
|
export * from './boolean-both-query.model'
|
||||||
export * from './search-target-query.model'
|
export * from './search-target-query.model'
|
||||||
|
export * from './videos-common-query.model'
|
||||||
export * from './videos-search-query.model'
|
export * from './videos-search-query.model'
|
||||||
export * from './video-channels-search-query.model'
|
export * from './video-channels-search-query.model'
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
export type NSFWQuery = 'true' | 'false' | 'both'
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
import { VideoFilter } from '../videos'
|
||||||
|
import { BooleanBothQuery } from './boolean-both-query.model'
|
||||||
|
|
||||||
|
// These query parameters can be used with any endpoint that list videos
|
||||||
|
export interface VideosCommonQuery {
|
||||||
|
start?: number
|
||||||
|
count?: number
|
||||||
|
sort?: string
|
||||||
|
|
||||||
|
nsfw?: BooleanBothQuery
|
||||||
|
|
||||||
|
isLive?: boolean
|
||||||
|
|
||||||
|
categoryOneOf?: number[]
|
||||||
|
|
||||||
|
licenceOneOf?: number[]
|
||||||
|
|
||||||
|
languageOneOf?: string[]
|
||||||
|
|
||||||
|
tagsOneOf?: string[]
|
||||||
|
tagsAllOf?: string[]
|
||||||
|
|
||||||
|
filter?: VideoFilter
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface VideosWithSearchCommonQuery extends VideosCommonQuery {
|
||||||
|
search?: string
|
||||||
|
}
|
|
@ -1,33 +1,15 @@
|
||||||
import { VideoFilter } from '../videos'
|
|
||||||
import { NSFWQuery } from './nsfw-query.model'
|
|
||||||
import { SearchTargetQuery } from './search-target-query.model'
|
import { SearchTargetQuery } from './search-target-query.model'
|
||||||
|
import { VideosCommonQuery } from './videos-common-query.model'
|
||||||
|
|
||||||
export interface VideosSearchQuery extends SearchTargetQuery {
|
export interface VideosSearchQuery extends SearchTargetQuery, VideosCommonQuery {
|
||||||
search?: string
|
search?: string
|
||||||
|
|
||||||
start?: number
|
|
||||||
count?: number
|
|
||||||
sort?: string
|
|
||||||
|
|
||||||
startDate?: string // ISO 8601
|
startDate?: string // ISO 8601
|
||||||
endDate?: string // ISO 8601
|
endDate?: string // ISO 8601
|
||||||
|
|
||||||
originallyPublishedStartDate?: string // ISO 8601
|
originallyPublishedStartDate?: string // ISO 8601
|
||||||
originallyPublishedEndDate?: string // ISO 8601
|
originallyPublishedEndDate?: string // ISO 8601
|
||||||
|
|
||||||
nsfw?: NSFWQuery
|
|
||||||
|
|
||||||
categoryOneOf?: number[]
|
|
||||||
|
|
||||||
licenceOneOf?: number[]
|
|
||||||
|
|
||||||
languageOneOf?: string[]
|
|
||||||
|
|
||||||
tagsOneOf?: string[]
|
|
||||||
tagsAllOf?: string[]
|
|
||||||
|
|
||||||
durationMin?: number // seconds
|
durationMin?: number // seconds
|
||||||
durationMax?: number // seconds
|
durationMax?: number // seconds
|
||||||
|
|
||||||
filter?: VideoFilter
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -210,6 +210,7 @@ paths:
|
||||||
parameters:
|
parameters:
|
||||||
- $ref: '#/components/parameters/name'
|
- $ref: '#/components/parameters/name'
|
||||||
- $ref: '#/components/parameters/categoryOneOf'
|
- $ref: '#/components/parameters/categoryOneOf'
|
||||||
|
- $ref: '#/components/parameters/isLive'
|
||||||
- $ref: '#/components/parameters/tagsOneOf'
|
- $ref: '#/components/parameters/tagsOneOf'
|
||||||
- $ref: '#/components/parameters/tagsAllOf'
|
- $ref: '#/components/parameters/tagsAllOf'
|
||||||
- $ref: '#/components/parameters/licenceOneOf'
|
- $ref: '#/components/parameters/licenceOneOf'
|
||||||
|
@ -781,6 +782,7 @@ paths:
|
||||||
- Videos
|
- Videos
|
||||||
parameters:
|
parameters:
|
||||||
- $ref: '#/components/parameters/categoryOneOf'
|
- $ref: '#/components/parameters/categoryOneOf'
|
||||||
|
- $ref: '#/components/parameters/isLive'
|
||||||
- $ref: '#/components/parameters/tagsOneOf'
|
- $ref: '#/components/parameters/tagsOneOf'
|
||||||
- $ref: '#/components/parameters/tagsAllOf'
|
- $ref: '#/components/parameters/tagsAllOf'
|
||||||
- $ref: '#/components/parameters/licenceOneOf'
|
- $ref: '#/components/parameters/licenceOneOf'
|
||||||
|
@ -1086,6 +1088,7 @@ paths:
|
||||||
- Video
|
- Video
|
||||||
parameters:
|
parameters:
|
||||||
- $ref: '#/components/parameters/categoryOneOf'
|
- $ref: '#/components/parameters/categoryOneOf'
|
||||||
|
- $ref: '#/components/parameters/isLive'
|
||||||
- $ref: '#/components/parameters/tagsOneOf'
|
- $ref: '#/components/parameters/tagsOneOf'
|
||||||
- $ref: '#/components/parameters/tagsAllOf'
|
- $ref: '#/components/parameters/tagsAllOf'
|
||||||
- $ref: '#/components/parameters/licenceOneOf'
|
- $ref: '#/components/parameters/licenceOneOf'
|
||||||
|
@ -2194,6 +2197,7 @@ paths:
|
||||||
parameters:
|
parameters:
|
||||||
- $ref: '#/components/parameters/channelHandle'
|
- $ref: '#/components/parameters/channelHandle'
|
||||||
- $ref: '#/components/parameters/categoryOneOf'
|
- $ref: '#/components/parameters/categoryOneOf'
|
||||||
|
- $ref: '#/components/parameters/isLive'
|
||||||
- $ref: '#/components/parameters/tagsOneOf'
|
- $ref: '#/components/parameters/tagsOneOf'
|
||||||
- $ref: '#/components/parameters/tagsAllOf'
|
- $ref: '#/components/parameters/tagsAllOf'
|
||||||
- $ref: '#/components/parameters/licenceOneOf'
|
- $ref: '#/components/parameters/licenceOneOf'
|
||||||
|
@ -2841,6 +2845,7 @@ paths:
|
||||||
schema:
|
schema:
|
||||||
type: string
|
type: string
|
||||||
- $ref: '#/components/parameters/categoryOneOf'
|
- $ref: '#/components/parameters/categoryOneOf'
|
||||||
|
- $ref: '#/components/parameters/isLive'
|
||||||
- $ref: '#/components/parameters/tagsOneOf'
|
- $ref: '#/components/parameters/tagsOneOf'
|
||||||
- $ref: '#/components/parameters/tagsAllOf'
|
- $ref: '#/components/parameters/tagsAllOf'
|
||||||
- $ref: '#/components/parameters/licenceOneOf'
|
- $ref: '#/components/parameters/licenceOneOf'
|
||||||
|
@ -3809,6 +3814,13 @@ components:
|
||||||
description: The comment id
|
description: The comment id
|
||||||
schema:
|
schema:
|
||||||
type: integer
|
type: integer
|
||||||
|
isLive:
|
||||||
|
name: isLive
|
||||||
|
in: query
|
||||||
|
required: false
|
||||||
|
description: whether or not the video is a live
|
||||||
|
schema:
|
||||||
|
type: boolean
|
||||||
categoryOneOf:
|
categoryOneOf:
|
||||||
name: categoryOneOf
|
name: categoryOneOf
|
||||||
in: query
|
in: query
|
||||||
|
|
Loading…
Reference in New Issue