aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--client/src/app/+admin/admin.component.ts12
-rw-r--r--client/src/app/+admin/admin.module.ts8
-rw-r--r--client/src/app/+admin/moderation/video-block-list/video-block-list.component.html29
-rw-r--r--client/src/app/+admin/moderation/video-block-list/video-block-list.component.ts13
-rw-r--r--client/src/app/+admin/overview/index.ts1
-rw-r--r--client/src/app/+admin/overview/overview.routes.ts4
-rw-r--r--client/src/app/+admin/overview/videos/index.ts2
-rw-r--r--client/src/app/+admin/overview/videos/video-list.component.html86
-rw-r--r--client/src/app/+admin/overview/videos/video-list.component.scss10
-rw-r--r--client/src/app/+admin/overview/videos/video-list.component.ts123
-rw-r--r--client/src/app/+admin/overview/videos/video.routes.ts30
-rw-r--r--client/src/app/shared/shared-abuse-list/abuse-details.component.html4
-rw-r--r--client/src/app/shared/shared-abuse-list/abuse-list-table.component.html40
-rw-r--r--client/src/app/shared/shared-abuse-list/abuse-list-table.component.ts18
-rw-r--r--client/src/app/shared/shared-abuse-list/processed-abuse.model.ts4
-rw-r--r--client/src/app/shared/shared-abuse-list/shared-abuse-list.module.ts6
-rw-r--r--client/src/app/shared/shared-main/shared-main.module.ts13
-rw-r--r--client/src/app/shared/shared-main/video/embed.component.html3
-rw-r--r--client/src/app/shared/shared-main/video/embed.component.scss10
-rw-r--r--client/src/app/shared/shared-main/video/embed.component.ts35
-rw-r--r--client/src/app/shared/shared-main/video/index.ts1
-rw-r--r--client/src/app/shared/shared-main/video/video.service.ts85
-rw-r--r--client/src/app/shared/shared-moderation/moderation.scss77
-rw-r--r--client/src/app/shared/shared-moderation/report-modals/report.component.scss6
-rw-r--r--client/src/app/shared/shared-moderation/report-modals/video-report.component.html4
-rw-r--r--client/src/app/shared/shared-moderation/report-modals/video-report.component.ts21
-rw-r--r--client/src/app/shared/shared-tables/index.ts3
-rw-r--r--client/src/app/shared/shared-tables/shared-tables.module.ts25
-rw-r--r--client/src/app/shared/shared-tables/table-expander-icon.component.ts12
-rw-r--r--client/src/app/shared/shared-tables/video-cell.component.html19
-rw-r--r--client/src/app/shared/shared-tables/video-cell.component.scss74
-rw-r--r--client/src/app/shared/shared-tables/video-cell.component.ts15
32 files changed, 582 insertions, 211 deletions
diff --git a/client/src/app/+admin/admin.component.ts b/client/src/app/+admin/admin.component.ts
index 27d5e0a10..b8a957d1c 100644
--- a/client/src/app/+admin/admin.component.ts
+++ b/client/src/app/+admin/admin.component.ts
@@ -44,6 +44,14 @@ export class AdminComponent implements OnInit {
44 }) 44 })
45 } 45 }
46 46
47 if (this.hasVideosRight()) {
48 overviewItems.children.push({
49 label: $localize`Videos`,
50 routerLink: '/admin/videos',
51 iconName: 'videos'
52 })
53 }
54
47 if (overviewItems.children.length !== 0) { 55 if (overviewItems.children.length !== 0) {
48 this.menuEntries.push(overviewItems) 56 this.menuEntries.push(overviewItems)
49 } 57 }
@@ -217,4 +225,8 @@ export class AdminComponent implements OnInit {
217 private hasVideoCommentsRight () { 225 private hasVideoCommentsRight () {
218 return this.auth.getUser().hasRight(UserRight.SEE_ALL_COMMENTS) 226 return this.auth.getUser().hasRight(UserRight.SEE_ALL_COMMENTS)
219 } 227 }
228
229 private hasVideosRight () {
230 return this.auth.getUser().hasRight(UserRight.SEE_ALL_VIDEOS)
231 }
220} 232}
diff --git a/client/src/app/+admin/admin.module.ts b/client/src/app/+admin/admin.module.ts
index a2bd88880..d04c11a20 100644
--- a/client/src/app/+admin/admin.module.ts
+++ b/client/src/app/+admin/admin.module.ts
@@ -10,7 +10,9 @@ import { SharedFormModule } from '@app/shared/shared-forms'
10import { SharedGlobalIconModule } from '@app/shared/shared-icons' 10import { SharedGlobalIconModule } from '@app/shared/shared-icons'
11import { SharedMainModule } from '@app/shared/shared-main' 11import { SharedMainModule } from '@app/shared/shared-main'
12import { SharedModerationModule } from '@app/shared/shared-moderation' 12import { SharedModerationModule } from '@app/shared/shared-moderation'
13import { SharedTablesModule } from '@app/shared/shared-tables'
13import { SharedVideoCommentModule } from '@app/shared/shared-video-comment' 14import { SharedVideoCommentModule } from '@app/shared/shared-video-comment'
15import { SharedVideoMiniatureModule } from '@app/shared/shared-video-miniature'
14import { AdminRoutingModule } from './admin-routing.module' 16import { AdminRoutingModule } from './admin-routing.module'
15import { AdminComponent } from './admin.component' 17import { AdminComponent } from './admin.component'
16import { 18import {
@@ -33,7 +35,7 @@ import { AbuseListComponent, VideoBlockListComponent } from './moderation'
33import { InstanceAccountBlocklistComponent, InstanceServerBlocklistComponent } from './moderation/instance-blocklist' 35import { InstanceAccountBlocklistComponent, InstanceServerBlocklistComponent } from './moderation/instance-blocklist'
34import { ModerationComponent } from './moderation/moderation.component' 36import { ModerationComponent } from './moderation/moderation.component'
35import { VideoCommentListComponent } from './moderation/video-comment-list' 37import { VideoCommentListComponent } from './moderation/video-comment-list'
36import { UserCreateComponent, UserListComponent, UserPasswordComponent, UserUpdateComponent } from './overview' 38import { UserCreateComponent, UserListComponent, UserPasswordComponent, UserUpdateComponent, VideoListComponent } from './overview'
37import { PluginListInstalledComponent } from './plugins/plugin-list-installed/plugin-list-installed.component' 39import { PluginListInstalledComponent } from './plugins/plugin-list-installed/plugin-list-installed.component'
38import { PluginSearchComponent } from './plugins/plugin-search/plugin-search.component' 40import { PluginSearchComponent } from './plugins/plugin-search/plugin-search.component'
39import { PluginShowInstalledComponent } from './plugins/plugin-show-installed/plugin-show-installed.component' 41import { PluginShowInstalledComponent } from './plugins/plugin-show-installed/plugin-show-installed.component'
@@ -56,6 +58,8 @@ import { JobsComponent } from './system/jobs/jobs.component'
56 SharedActorImageModule, 58 SharedActorImageModule,
57 SharedActorImageEditModule, 59 SharedActorImageEditModule,
58 SharedCustomMarkupModule, 60 SharedCustomMarkupModule,
61 SharedVideoMiniatureModule,
62 SharedTablesModule,
59 63
60 TableModule, 64 TableModule,
61 SelectButtonModule, 65 SelectButtonModule,
@@ -65,6 +69,8 @@ import { JobsComponent } from './system/jobs/jobs.component'
65 declarations: [ 69 declarations: [
66 AdminComponent, 70 AdminComponent,
67 71
72 VideoListComponent,
73
68 FollowsComponent, 74 FollowsComponent,
69 FollowersListComponent, 75 FollowersListComponent,
70 FollowingListComponent, 76 FollowingListComponent,
diff --git a/client/src/app/+admin/moderation/video-block-list/video-block-list.component.html b/client/src/app/+admin/moderation/video-block-list/video-block-list.component.html
index 7efa87dd0..3cd69cfbc 100644
--- a/client/src/app/+admin/moderation/video-block-list/video-block-list.component.html
+++ b/client/src/app/+admin/moderation/video-block-list/video-block-list.component.html
@@ -34,9 +34,7 @@
34 <tr> 34 <tr>
35 <td *ngIf="!videoBlock.reason"></td> 35 <td *ngIf="!videoBlock.reason"></td>
36 <td *ngIf="videoBlock.reason" class="expand-cell c-hand" [pRowToggler]="videoBlock" i18n-ngbTooltip ngbTooltip="More information" placement="top-left" container="body"> 36 <td *ngIf="videoBlock.reason" class="expand-cell c-hand" [pRowToggler]="videoBlock" i18n-ngbTooltip ngbTooltip="More information" placement="top-left" container="body">
37 <span class="expander"> 37 <my-table-expander-icon [expanded]="expanded"></my-table-expander-icon>
38 <i [ngClass]="expanded ? 'glyphicon glyphicon-menu-down' : 'glyphicon glyphicon-menu-right'"></i>
39 </span>
40 </td> 38 </td>
41 39
42 <td class="action-cell"> 40 <td class="action-cell">
@@ -47,22 +45,11 @@
47 </td> 45 </td>
48 46
49 <td> 47 <td>
50 <a [href]="getVideoUrl(videoBlock)" class="table-video-link" [title]="videoBlock.video.name" target="_blank" rel="noopener noreferrer"> 48 <my-video-cell [video]="videoBlock.video">
51 <div class="table-video"> 49 <span name>
52 <div class="table-video-image"> 50 <my-global-icon *ngIf="videoBlock.type === 2" i18n-title title="The video was blocked due to automatic blocking of new videos" iconName="robot"></my-global-icon>
53 <img [src]="videoBlock.video.thumbnailPath"> 51 </span>
54 </div> 52 </my-video-cell>
55
56 <div class="table-video-text">
57 <div>
58 <my-global-icon i18n-title title="The video was blocked due to automatic blocking of new videos" *ngIf="videoBlock.type === 2" iconName="robot"></my-global-icon>
59 {{ videoBlock.video.name }}
60 </div>
61
62 <div class="text-muted">by {{ videoBlock.video.channel?.displayName }} on {{ videoBlock.video.channel?.host }} </div>
63 </div>
64 </div>
65 </a>
66 </td> 53 </td>
67 54
68 <td> 55 <td>
@@ -90,9 +77,7 @@
90 </div> 77 </div>
91 78
92 <div class="right"> 79 <div class="right">
93 <div class="screenratio"> 80 <my-embed [video]="videoBlock.video"></my-embed>
94 <div [innerHTML]="videoBlock.embedHtml"></div>
95 </div>
96 </div> 81 </div>
97 82
98 </div> 83 </div>
diff --git a/client/src/app/+admin/moderation/video-block-list/video-block-list.component.ts b/client/src/app/+admin/moderation/video-block-list/video-block-list.component.ts
index 7baf34ca2..1fe8d0f9d 100644
--- a/client/src/app/+admin/moderation/video-block-list/video-block-list.component.ts
+++ b/client/src/app/+admin/moderation/video-block-list/video-block-list.component.ts
@@ -3,11 +3,10 @@ import { switchMap } from 'rxjs/operators'
3import { buildVideoOrPlaylistEmbed } from 'src/assets/player/utils' 3import { buildVideoOrPlaylistEmbed } from 'src/assets/player/utils'
4import { environment } from 'src/environments/environment' 4import { environment } from 'src/environments/environment'
5import { Component, OnInit } from '@angular/core' 5import { Component, OnInit } from '@angular/core'
6import { DomSanitizer } from '@angular/platform-browser'
7import { ActivatedRoute, Router } from '@angular/router' 6import { ActivatedRoute, Router } from '@angular/router'
8import { ConfirmService, MarkdownService, Notifier, RestPagination, RestTable, ServerService } from '@app/core' 7import { ConfirmService, MarkdownService, Notifier, RestPagination, RestTable, ServerService } from '@app/core'
9import { AdvancedInputFilter } from '@app/shared/shared-forms' 8import { AdvancedInputFilter } from '@app/shared/shared-forms'
10import { DropdownAction, Video, VideoService } from '@app/shared/shared-main' 9import { DropdownAction, VideoService } from '@app/shared/shared-main'
11import { VideoBlockService } from '@app/shared/shared-moderation' 10import { VideoBlockService } from '@app/shared/shared-moderation'
12import { buildVideoEmbedLink, decorateVideoLink } from '@shared/core-utils' 11import { buildVideoEmbedLink, decorateVideoLink } from '@shared/core-utils'
13import { VideoBlacklist, VideoBlacklistType } from '@shared/models' 12import { VideoBlacklist, VideoBlacklistType } from '@shared/models'
@@ -18,7 +17,7 @@ import { VideoBlacklist, VideoBlacklistType } from '@shared/models'
18 styleUrls: [ '../../../shared/shared-moderation/moderation.scss', './video-block-list.component.scss' ] 17 styleUrls: [ '../../../shared/shared-moderation/moderation.scss', './video-block-list.component.scss' ]
19}) 18})
20export class VideoBlockListComponent extends RestTable implements OnInit { 19export class VideoBlockListComponent extends RestTable implements OnInit {
21 blocklist: (VideoBlacklist & { reasonHtml?: string, embedHtml?: string })[] = [] 20 blocklist: (VideoBlacklist & { reasonHtml?: string })[] = []
22 totalRecords = 0 21 totalRecords = 0
23 sort: SortMeta = { field: 'createdAt', order: -1 } 22 sort: SortMeta = { field: 'createdAt', order: -1 }
24 pagination: RestPagination = { count: this.rowsPerPage, start: 0 } 23 pagination: RestPagination = { count: this.rowsPerPage, start: 0 }
@@ -50,7 +49,6 @@ export class VideoBlockListComponent extends RestTable implements OnInit {
50 private confirmService: ConfirmService, 49 private confirmService: ConfirmService,
51 private videoBlocklistService: VideoBlockService, 50 private videoBlocklistService: VideoBlockService,
52 private markdownRenderer: MarkdownService, 51 private markdownRenderer: MarkdownService,
53 private sanitizer: DomSanitizer,
54 private videoService: VideoService 52 private videoService: VideoService
55 ) { 53 ) {
56 super() 54 super()
@@ -125,10 +123,6 @@ export class VideoBlockListComponent extends RestTable implements OnInit {
125 return 'VideoBlockListComponent' 123 return 'VideoBlockListComponent'
126 } 124 }
127 125
128 getVideoUrl (videoBlock: VideoBlacklist) {
129 return Video.buildWatchUrl(videoBlock.video)
130 }
131
132 toHtml (text: string) { 126 toHtml (text: string) {
133 return this.markdownRenderer.textMarkdownToHTML(text) 127 return this.markdownRenderer.textMarkdownToHTML(text)
134 } 128 }
@@ -176,8 +170,7 @@ export class VideoBlockListComponent extends RestTable implements OnInit {
176 170
177 for (const element of this.blocklist) { 171 for (const element of this.blocklist) {
178 Object.assign(element, { 172 Object.assign(element, {
179 reasonHtml: await this.toHtml(element.reason), 173 reasonHtml: await this.toHtml(element.reason)
180 embedHtml: this.sanitizer.bypassSecurityTrustHtml(this.getVideoEmbed(element))
181 }) 174 })
182 } 175 }
183 }, 176 },
diff --git a/client/src/app/+admin/overview/index.ts b/client/src/app/+admin/overview/index.ts
index b71a6a45f..a9c46893f 100644
--- a/client/src/app/+admin/overview/index.ts
+++ b/client/src/app/+admin/overview/index.ts
@@ -1,2 +1,3 @@
1export * from './users' 1export * from './users'
2export * from './videos'
2export * from './overview.routes' 3export * from './overview.routes'
diff --git a/client/src/app/+admin/overview/overview.routes.ts b/client/src/app/+admin/overview/overview.routes.ts
index cb5986072..1e6686d16 100644
--- a/client/src/app/+admin/overview/overview.routes.ts
+++ b/client/src/app/+admin/overview/overview.routes.ts
@@ -1,6 +1,8 @@
1import { Routes } from '@angular/router' 1import { Routes } from '@angular/router'
2import { UsersRoutes } from './users' 2import { UsersRoutes } from './users'
3import { VideosRoutes } from './videos'
3 4
4export const OverviewRoutes: Routes = [ 5export const OverviewRoutes: Routes = [
5 ...UsersRoutes 6 ...UsersRoutes,
7 ...VideosRoutes
6] 8]
diff --git a/client/src/app/+admin/overview/videos/index.ts b/client/src/app/+admin/overview/videos/index.ts
new file mode 100644
index 000000000..40c2ffe72
--- /dev/null
+++ b/client/src/app/+admin/overview/videos/index.ts
@@ -0,0 +1,2 @@
1export * from './video-list.component'
2export * from './video.routes'
diff --git a/client/src/app/+admin/overview/videos/video-list.component.html b/client/src/app/+admin/overview/videos/video-list.component.html
new file mode 100644
index 000000000..1f1e9cc6e
--- /dev/null
+++ b/client/src/app/+admin/overview/videos/video-list.component.html
@@ -0,0 +1,86 @@
1<h1>
2 <my-global-icon iconName="videos" aria-hidden="true"></my-global-icon>
3 <ng-container i18n>Videos</ng-container>
4</h1>
5
6<p-table
7 [value]="videos" [paginator]="totalRecords > 0" [totalRecords]="totalRecords" [rows]="rowsPerPage" [rowsPerPageOptions]="rowsPerPageOptions"
8 [sortField]="sort.field" [sortOrder]="sort.order" dataKey="id" [resizableColumns]="true" [(selection)]="selectedVideos"
9 [lazy]="true" (onLazyLoad)="loadLazy($event)" [lazyLoadOnInit]="false"
10 [showCurrentPageReport]="true" i18n-currentPageReportTemplate
11 currentPageReportTemplate="Showing {{'{first}'}} to {{'{last}'}} of {{'{totalRecords}'}} videos"
12 (onPage)="onPage($event)" [expandedRowKeys]="expandedRows"
13>
14 <ng-template pTemplate="caption">
15 <div class="caption">
16 <div class="left-buttons">
17 <my-action-dropdown
18 *ngIf="isInSelectionMode()" i18n-label label="Batch actions" theme="orange"
19 [actions]="bulkVideoActions" [entry]="selectedVideos"
20 >
21 </my-action-dropdown>
22 </div>
23
24 <div class="ml-auto">
25 <my-advanced-input-filter [filters]="inputFilters" (search)="onSearch($event)"></my-advanced-input-filter>
26 </div>
27
28 </div>
29 </ng-template>
30
31 <ng-template pTemplate="header">
32 <tr>
33 <th style="width: 40px">
34 <p-tableHeaderCheckbox ariaLabel="Select all rows" i18n-ariaLabel></p-tableHeaderCheckbox>
35 </th>
36 <th style="width: 40px"></th>
37 <th style="width: 60px;"></th>
38 <th i18n>Video</th>
39 <th i18n>Info</th>
40 <th style="width: 150px;" i18n pSortableColumn="publishedAt">Published <p-sortIcon field="publishedAt"></p-sortIcon></th>
41 </tr>
42 </ng-template>
43
44 <ng-template pTemplate="body" let-expanded="expanded" let-video>
45
46 <tr [pSelectableRow]="video">
47 <td class="checkbox-cell">
48 <p-tableCheckbox [value]="video" ariaLabel="Select this row" i18n-ariaLabel></p-tableCheckbox>
49 </td>
50
51 <td class="expand-cell" [pRowToggler]="video">
52 <my-table-expander-icon [expanded]="expanded"></my-table-expander-icon>
53 </td>
54
55 <td class="action-cell">
56 <my-video-actions-dropdown
57 placement="bottom auto" buttonDirection="horizontal" [buttonStyled]="true" [video]="video"
58 [displayOptions]="videoActionsOptions" (videoRemoved)="onVideoRemoved()"
59 ></my-video-actions-dropdown>
60 </td>
61
62 <td>
63 <my-video-cell [video]="video"></my-video-cell>
64 </td>
65
66 <td>
67 <span class="badge badge-blue" i18n>{{ video.privacy.label }}</span>
68 <span *ngIf="video.nsfw" class="badge badge-red" i18n>NSFW</span>
69 <span *ngIf="video.blocked" class="badge badge-red" i18n>NSFW</span>
70 </td>
71
72 <td>
73 {{ video.publishedAt | date: 'short' }}
74 </td>
75
76 </tr>
77 </ng-template>
78
79 <ng-template pTemplate="rowexpansion" let-video>
80 <tr>
81 <td colspan="50">
82 <my-embed [video]="video"></my-embed>
83 </td>
84 </tr>
85 </ng-template>
86</p-table>
diff --git a/client/src/app/+admin/overview/videos/video-list.component.scss b/client/src/app/+admin/overview/videos/video-list.component.scss
new file mode 100644
index 000000000..fcdb457f2
--- /dev/null
+++ b/client/src/app/+admin/overview/videos/video-list.component.scss
@@ -0,0 +1,10 @@
1@use '_variables' as *;
2@use '_mixins' as *;
3my-embed {
4 display: block;
5 max-width: 500px;
6}
7
8.badge {
9 @include table-badge;
10}
diff --git a/client/src/app/+admin/overview/videos/video-list.component.ts b/client/src/app/+admin/overview/videos/video-list.component.ts
new file mode 100644
index 000000000..a445bc209
--- /dev/null
+++ b/client/src/app/+admin/overview/videos/video-list.component.ts
@@ -0,0 +1,123 @@
1import { SortMeta } from 'primeng/api'
2import { Component, OnInit } from '@angular/core'
3import { ActivatedRoute, Router } from '@angular/router'
4import { AuthService, ConfirmService, Notifier, RestPagination, RestTable } from '@app/core'
5import { DropdownAction, Video, VideoService } from '@app/shared/shared-main'
6import { UserRight } from '@shared/models'
7import { AdvancedInputFilter } from '@app/shared/shared-forms'
8import { VideoActionsDisplayType } from '@app/shared/shared-video-miniature'
9
10@Component({
11 selector: 'my-video-list',
12 templateUrl: './video-list.component.html',
13 styleUrls: [ './video-list.component.scss' ]
14})
15export class VideoListComponent extends RestTable implements OnInit {
16 videos: Video[] = []
17
18 totalRecords = 0
19 sort: SortMeta = { field: 'publishedAt', order: 1 }
20 pagination: RestPagination = { count: this.rowsPerPage, start: 0 }
21
22 bulkVideoActions: DropdownAction<Video[]>[][] = []
23
24 selectedVideos: Video[] = []
25
26 inputFilters: AdvancedInputFilter[] = [
27 {
28 title: $localize`Advanced filters`,
29 children: [
30 {
31 queryParams: { search: 'local:true' },
32 label: $localize`Only local videos`
33 }
34 ]
35 }
36 ]
37
38 videoActionsOptions: VideoActionsDisplayType = {
39 playlist: false,
40 download: false,
41 update: true,
42 blacklist: true,
43 delete: true,
44 report: false,
45 duplicate: true,
46 mute: true,
47 liveInfo: false
48 }
49
50 constructor (
51 protected route: ActivatedRoute,
52 protected router: Router,
53 private confirmService: ConfirmService,
54 private auth: AuthService,
55 private notifier: Notifier,
56 private videoService: VideoService
57 ) {
58 super()
59 }
60
61 get authUser () {
62 return this.auth.getUser()
63 }
64
65 ngOnInit () {
66 this.initialize()
67
68 this.bulkVideoActions = [
69 [
70 {
71 label: $localize`Delete`,
72 handler: videos => this.removeVideos(videos),
73 isDisplayed: () => this.authUser.hasRight(UserRight.REMOVE_ANY_VIDEO)
74 }
75 ]
76 ]
77 }
78
79 getIdentifier () {
80 return 'VideoListComponent'
81 }
82
83 isInSelectionMode () {
84 return this.selectedVideos.length !== 0
85 }
86
87 onVideoRemoved () {
88 this.reloadData()
89 }
90
91 protected reloadData () {
92 this.selectedVideos = []
93
94 this.videoService.getAdminVideos({
95 pagination: this.pagination,
96 sort: this.sort,
97 search: this.search
98 }).subscribe({
99 next: resultList => {
100 this.videos = resultList.data
101 this.totalRecords = resultList.total
102 },
103
104 error: err => this.notifier.error(err.message)
105 })
106 }
107
108 private async removeVideos (videos: Video[]) {
109 const message = $localize`Are you sure you want to delete these ${videos.length} videos?`
110 const res = await this.confirmService.confirm(message, $localize`Delete`)
111 if (res === false) return
112
113 this.videoService.removeVideo(videos.map(v => v.id))
114 .subscribe({
115 next: () => {
116 this.notifier.success($localize`${videos.length} videos deleted.`)
117 this.reloadData()
118 },
119
120 error: err => this.notifier.error(err.message)
121 })
122 }
123}
diff --git a/client/src/app/+admin/overview/videos/video.routes.ts b/client/src/app/+admin/overview/videos/video.routes.ts
new file mode 100644
index 000000000..984df7b82
--- /dev/null
+++ b/client/src/app/+admin/overview/videos/video.routes.ts
@@ -0,0 +1,30 @@
1import { Routes } from '@angular/router'
2import { UserRightGuard } from '@app/core'
3import { UserRight } from '@shared/models'
4import { VideoListComponent } from './video-list.component'
5
6export const VideosRoutes: Routes = [
7 {
8 path: 'videos',
9 canActivate: [ UserRightGuard ],
10 data: {
11 userRight: UserRight.SEE_ALL_VIDEOS
12 },
13 children: [
14 {
15 path: '',
16 redirectTo: 'list',
17 pathMatch: 'full'
18 },
19 {
20 path: 'list',
21 component: VideoListComponent,
22 data: {
23 meta: {
24 title: $localize`Videos list`
25 }
26 }
27 }
28 ]
29 }
30]
diff --git a/client/src/app/shared/shared-abuse-list/abuse-details.component.html b/client/src/app/shared/shared-abuse-list/abuse-details.component.html
index 2f0bc5ae5..a1a4586f0 100644
--- a/client/src/app/shared/shared-abuse-list/abuse-details.component.html
+++ b/client/src/app/shared/shared-abuse-list/abuse-details.component.html
@@ -85,9 +85,9 @@
85 85
86 <!-- report right part (video/comment details) --> 86 <!-- report right part (video/comment details) -->
87 <div class="right"> 87 <div class="right">
88 <div *ngIf="abuse.video" class="screenratio"> 88 <div *ngIf="abuse.video">
89 <div *ngIf="abuse.video.deleted" i18n>The video was deleted</div> 89 <div *ngIf="abuse.video.deleted" i18n>The video was deleted</div>
90 <div *ngIf="!abuse.video.deleted" [innerHTML]="abuse.embedHtml"></div> 90 <my-embed *ngIf="!abuse.video.deleted" [video]="abuse.video"></my-embed>
91 </div> 91 </div>
92 92
93 <div *ngIf="abuse.comment" class="comment-html"> 93 <div *ngIf="abuse.comment" class="comment-html">
diff --git a/client/src/app/shared/shared-abuse-list/abuse-list-table.component.html b/client/src/app/shared/shared-abuse-list/abuse-list-table.component.html
index 4bf83316b..d957eaeab 100644
--- a/client/src/app/shared/shared-abuse-list/abuse-list-table.component.html
+++ b/client/src/app/shared/shared-abuse-list/abuse-list-table.component.html
@@ -30,9 +30,7 @@
30 <ng-template pTemplate="body" let-expanded="expanded" let-abuse> 30 <ng-template pTemplate="body" let-expanded="expanded" let-abuse>
31 <tr> 31 <tr>
32 <td class="expand-cell c-hand" [pRowToggler]="abuse" i18n-ngbTooltip ngbTooltip="More information" placement="top-left" container="body"> 32 <td class="expand-cell c-hand" [pRowToggler]="abuse" i18n-ngbTooltip ngbTooltip="More information" placement="top-left" container="body">
33 <span class="expander"> 33 <my-table-expander-icon [expanded]="expanded"></my-table-expander-icon>
34 <i [ngClass]="expanded ? 'glyphicon glyphicon-menu-down' : 'glyphicon glyphicon-menu-right'"></i>
35 </span>
36 </td> 34 </td>
37 35
38 <td class="action-cell"> 36 <td class="action-cell">
@@ -61,28 +59,20 @@
61 <ng-container *ngIf="abuse.video"> 59 <ng-container *ngIf="abuse.video">
62 60
63 <td *ngIf="!abuse.video.deleted"> 61 <td *ngIf="!abuse.video.deleted">
64 <a [href]="getVideoUrl(abuse)" class="table-video-link" [title]="abuse.video.name" target="_blank" rel="noopener noreferrer"> 62 <my-video-cell [video]="abuse.video">
65 <div class="table-video"> 63 <span image>
66 <div class="table-video-image"> 64 <span
67 <img [src]="abuse.video.thumbnailPath"> 65 class="table-video-image-label" *ngIf="abuse.count > 1"
68 <span 66 i18n-title title="This video has been reported multiple times."
69 class="table-video-image-label" *ngIf="abuse.count > 1" 67 >
70 i18n-title title="This video has been reported multiple times." 68 {{ abuse.nth }}/{{ abuse.count }}
71 > 69 </span>
72 {{ abuse.nth }}/{{ abuse.count }} 70 </span>
73 </span> 71
74 </div> 72 <span name>
75 73 <span *ngIf="abuse.video.blacklisted" i18n-title title="The video was blocked" class="glyphicon glyphicon-ban-circle"></span>
76 <div class="table-video-text"> 74 </span>
77 <div> 75 </my-video-cell>
78 <span *ngIf="!abuse.video.blacklisted" class="glyphicon glyphicon-new-window"></span>
79 <span *ngIf="abuse.video.blacklisted" i18n-title title="The video was blocked" class="glyphicon glyphicon-ban-circle"></span>
80 {{ abuse.video.name }}
81 </div>
82 <div i18n>by {{ abuse.video.channel?.displayName }} on {{ abuse.video.channel?.host }} </div>
83 </div>
84 </div>
85 </a>
86 </td> 76 </td>
87 77
88 <td *ngIf="abuse.video.deleted" class="c-hand" [pRowToggler]="abuse"> 78 <td *ngIf="abuse.video.deleted" class="c-hand" [pRowToggler]="abuse">
diff --git a/client/src/app/shared/shared-abuse-list/abuse-list-table.component.ts b/client/src/app/shared/shared-abuse-list/abuse-list-table.component.ts
index 297993e39..10f5861b9 100644
--- a/client/src/app/shared/shared-abuse-list/abuse-list-table.component.ts
+++ b/client/src/app/shared/shared-abuse-list/abuse-list-table.component.ts
@@ -1,8 +1,6 @@
1import * as debug from 'debug' 1import * as debug from 'debug'
2import truncate from 'lodash-es/truncate' 2import truncate from 'lodash-es/truncate'
3import { SortMeta } from 'primeng/api' 3import { SortMeta } from 'primeng/api'
4import { buildVideoOrPlaylistEmbed } from 'src/assets/player/utils'
5import { environment } from 'src/environments/environment'
6import { Component, Input, OnInit, ViewChild } from '@angular/core' 4import { Component, Input, OnInit, ViewChild } from '@angular/core'
7import { DomSanitizer } from '@angular/platform-browser' 5import { DomSanitizer } from '@angular/platform-browser'
8import { ActivatedRoute, Router } from '@angular/router' 6import { ActivatedRoute, Router } from '@angular/router'
@@ -10,7 +8,6 @@ import { ConfirmService, MarkdownService, Notifier, RestPagination, RestTable }
10import { Account, Actor, DropdownAction, Video, VideoService } from '@app/shared/shared-main' 8import { Account, Actor, DropdownAction, Video, VideoService } from '@app/shared/shared-main'
11import { AbuseService, BlocklistService, VideoBlockService } from '@app/shared/shared-moderation' 9import { AbuseService, BlocklistService, VideoBlockService } from '@app/shared/shared-moderation'
12import { VideoCommentService } from '@app/shared/shared-video-comment' 10import { VideoCommentService } from '@app/shared/shared-video-comment'
13import { buildVideoEmbedLink, decorateVideoLink } from '@shared/core-utils'
14import { AbuseState, AdminAbuse } from '@shared/models' 11import { AbuseState, AdminAbuse } from '@shared/models'
15import { AdvancedInputFilter } from '../shared-forms' 12import { AdvancedInputFilter } from '../shared-forms'
16import { AbuseMessageModalComponent } from './abuse-message-modal.component' 13import { AbuseMessageModalComponent } from './abuse-message-modal.component'
@@ -133,19 +130,6 @@ export class AbuseListTableComponent extends RestTable implements OnInit {
133 return '/a/' + abuse.flaggedAccount.nameWithHost 130 return '/a/' + abuse.flaggedAccount.nameWithHost
134 } 131 }
135 132
136 getVideoEmbed (abuse: AdminAbuse) {
137 return buildVideoOrPlaylistEmbed(
138 decorateVideoLink({
139 url: buildVideoEmbedLink(abuse.video, environment.originServerUrl),
140 title: false,
141 warningTitle: false,
142 startTime: abuse.video.startAt,
143 stopTime: abuse.video.endAt
144 }),
145 abuse.video.name
146 )
147 }
148
149 async removeAbuse (abuse: AdminAbuse) { 133 async removeAbuse (abuse: AdminAbuse) {
150 const res = await this.confirmService.confirm($localize`Do you really want to delete this abuse report?`, $localize`Delete`) 134 const res = await this.confirmService.confirm($localize`Do you really want to delete this abuse report?`, $localize`Delete`)
151 if (res === false) return 135 if (res === false) return
@@ -220,8 +204,6 @@ export class AbuseListTableComponent extends RestTable implements OnInit {
220 } 204 }
221 205
222 if (abuse.video) { 206 if (abuse.video) {
223 abuse.embedHtml = this.sanitizer.bypassSecurityTrustHtml(this.getVideoEmbed(abuse))
224
225 if (abuse.video.channel?.ownerAccount) { 207 if (abuse.video.channel?.ownerAccount) {
226 abuse.video.channel.ownerAccount = new Account(abuse.video.channel.ownerAccount) 208 abuse.video.channel.ownerAccount = new Account(abuse.video.channel.ownerAccount)
227 } 209 }
diff --git a/client/src/app/shared/shared-abuse-list/processed-abuse.model.ts b/client/src/app/shared/shared-abuse-list/processed-abuse.model.ts
index 194d52a33..b9a9bd889 100644
--- a/client/src/app/shared/shared-abuse-list/processed-abuse.model.ts
+++ b/client/src/app/shared/shared-abuse-list/processed-abuse.model.ts
@@ -1,13 +1,11 @@
1import { SafeHtml } from '@angular/platform-browser'
2import { AdminAbuse } from '@shared/models'
3import { Account } from '@app/shared/shared-main' 1import { Account } from '@app/shared/shared-main'
2import { AdminAbuse } from '@shared/models'
4 3
5// Don't use an abuse model because we need external services to compute some properties 4// Don't use an abuse model because we need external services to compute some properties
6// And this model is only used in this component 5// And this model is only used in this component
7export type ProcessedAbuse = AdminAbuse & { 6export type ProcessedAbuse = AdminAbuse & {
8 moderationCommentHtml?: string 7 moderationCommentHtml?: string
9 reasonHtml?: string 8 reasonHtml?: string
10 embedHtml?: SafeHtml
11 updatedAt?: Date 9 updatedAt?: Date
12 10
13 // override bare server-side definitions with rich client-side definitions 11 // override bare server-side definitions with rich client-side definitions
diff --git a/client/src/app/shared/shared-abuse-list/shared-abuse-list.module.ts b/client/src/app/shared/shared-abuse-list/shared-abuse-list.module.ts
index 8f3830a17..eeda27fa6 100644
--- a/client/src/app/shared/shared-abuse-list/shared-abuse-list.module.ts
+++ b/client/src/app/shared/shared-abuse-list/shared-abuse-list.module.ts
@@ -1,16 +1,17 @@
1 1
2import { TableModule } from 'primeng/table' 2import { TableModule } from 'primeng/table'
3import { NgModule } from '@angular/core' 3import { NgModule } from '@angular/core'
4import { SharedActorImageModule } from '../shared-actor-image/shared-actor-image.module'
4import { SharedFormModule } from '../shared-forms/shared-form.module' 5import { SharedFormModule } from '../shared-forms/shared-form.module'
5import { SharedGlobalIconModule } from '../shared-icons' 6import { SharedGlobalIconModule } from '../shared-icons'
6import { SharedMainModule } from '../shared-main/shared-main.module' 7import { SharedMainModule } from '../shared-main/shared-main.module'
7import { SharedModerationModule } from '../shared-moderation' 8import { SharedModerationModule } from '../shared-moderation'
9import { SharedTablesModule } from '../shared-tables'
8import { SharedVideoCommentModule } from '../shared-video-comment' 10import { SharedVideoCommentModule } from '../shared-video-comment'
9import { AbuseDetailsComponent } from './abuse-details.component' 11import { AbuseDetailsComponent } from './abuse-details.component'
10import { AbuseListTableComponent } from './abuse-list-table.component' 12import { AbuseListTableComponent } from './abuse-list-table.component'
11import { AbuseMessageModalComponent } from './abuse-message-modal.component' 13import { AbuseMessageModalComponent } from './abuse-message-modal.component'
12import { ModerationCommentModalComponent } from './moderation-comment-modal.component' 14import { ModerationCommentModalComponent } from './moderation-comment-modal.component'
13import { SharedActorImageModule } from '../shared-actor-image/shared-actor-image.module'
14 15
15@NgModule({ 16@NgModule({
16 imports: [ 17 imports: [
@@ -21,7 +22,8 @@ import { SharedActorImageModule } from '../shared-actor-image/shared-actor-image
21 SharedModerationModule, 22 SharedModerationModule,
22 SharedGlobalIconModule, 23 SharedGlobalIconModule,
23 SharedVideoCommentModule, 24 SharedVideoCommentModule,
24 SharedActorImageModule 25 SharedActorImageModule,
26 SharedTablesModule
25 ], 27 ],
26 28
27 declarations: [ 29 declarations: [
diff --git a/client/src/app/shared/shared-main/shared-main.module.ts b/client/src/app/shared/shared-main/shared-main.module.ts
index 93989780d..a90b59e41 100644
--- a/client/src/app/shared/shared-main/shared-main.module.ts
+++ b/client/src/app/shared/shared-main/shared-main.module.ts
@@ -43,13 +43,8 @@ import {
43} from './misc' 43} from './misc'
44import { PluginPlaceholderComponent } from './plugins' 44import { PluginPlaceholderComponent } from './plugins'
45import { ActorRedirectGuard } from './router' 45import { ActorRedirectGuard } from './router'
46import { 46import { UserHistoryService, UserNotificationsComponent, UserNotificationService, UserQuotaComponent } from './users'
47 UserHistoryService, 47import { EmbedComponent, RedundancyService, VideoImportService, VideoOwnershipService, VideoService } from './video'
48 UserNotificationsComponent,
49 UserNotificationService,
50 UserQuotaComponent
51} from './users'
52import { RedundancyService, VideoImportService, VideoOwnershipService, VideoService } from './video'
53import { VideoCaptionService } from './video-caption' 48import { VideoCaptionService } from './video-caption'
54import { VideoChannelService } from './video-channel' 49import { VideoChannelService } from './video-channel'
55 50
@@ -111,6 +106,8 @@ import { VideoChannelService } from './video-channel'
111 UserQuotaComponent, 106 UserQuotaComponent,
112 UserNotificationsComponent, 107 UserNotificationsComponent,
113 108
109 EmbedComponent,
110
114 PluginPlaceholderComponent 111 PluginPlaceholderComponent
115 ], 112 ],
116 113
@@ -167,6 +164,8 @@ import { VideoChannelService } from './video-channel'
167 UserQuotaComponent, 164 UserQuotaComponent,
168 UserNotificationsComponent, 165 UserNotificationsComponent,
169 166
167 EmbedComponent,
168
170 PluginPlaceholderComponent 169 PluginPlaceholderComponent
171 ], 170 ],
172 171
diff --git a/client/src/app/shared/shared-main/video/embed.component.html b/client/src/app/shared/shared-main/video/embed.component.html
new file mode 100644
index 000000000..3b088d058
--- /dev/null
+++ b/client/src/app/shared/shared-main/video/embed.component.html
@@ -0,0 +1,3 @@
1<div class="screenratio">
2 <div [innerHTML]="embedHTML"></div>
3</div>
diff --git a/client/src/app/shared/shared-main/video/embed.component.scss b/client/src/app/shared/shared-main/video/embed.component.scss
new file mode 100644
index 000000000..420ba6f23
--- /dev/null
+++ b/client/src/app/shared/shared-main/video/embed.component.scss
@@ -0,0 +1,10 @@
1@use '_mixins' as *;
2@use '_variables' as *;
3
4.screenratio {
5 @include block-ratio($selector: 'div, ::ng-deep iframe') {
6 width: 100% !important;
7 height: 100% !important;
8 left: 0;
9 };
10}
diff --git a/client/src/app/shared/shared-main/video/embed.component.ts b/client/src/app/shared/shared-main/video/embed.component.ts
new file mode 100644
index 000000000..4732efa44
--- /dev/null
+++ b/client/src/app/shared/shared-main/video/embed.component.ts
@@ -0,0 +1,35 @@
1import { buildVideoOrPlaylistEmbed } from 'src/assets/player/utils'
2import { environment } from 'src/environments/environment'
3import { Component, Input, OnInit } from '@angular/core'
4import { DomSanitizer, SafeHtml } from '@angular/platform-browser'
5import { buildVideoEmbedLink, decorateVideoLink } from '@shared/core-utils'
6import { Video } from '@shared/models'
7
8@Component({
9 selector: 'my-embed',
10 styleUrls: [ './embed.component.scss' ],
11 templateUrl: './embed.component.html'
12})
13export class EmbedComponent implements OnInit {
14 @Input() video: Pick<Video, 'name' | 'uuid'>
15
16 embedHTML: SafeHtml
17
18 constructor (private sanitizer: DomSanitizer) {
19
20 }
21
22 ngOnInit () {
23 const html = buildVideoOrPlaylistEmbed(
24 decorateVideoLink({
25 url: buildVideoEmbedLink(this.video, environment.originServerUrl),
26
27 title: false,
28 warningTitle: false
29 }),
30 this.video.name
31 )
32
33 this.embedHTML = this.sanitizer.bypassSecurityTrustHtml(html)
34 }
35}
diff --git a/client/src/app/shared/shared-main/video/index.ts b/client/src/app/shared/shared-main/video/index.ts
index 3053df4ef..e72c0c3d6 100644
--- a/client/src/app/shared/shared-main/video/index.ts
+++ b/client/src/app/shared/shared-main/video/index.ts
@@ -1,3 +1,4 @@
1export * from './embed.component'
1export * from './redundancy.service' 2export * from './redundancy.service'
2export * from './video-details.model' 3export * from './video-details.model'
3export * from './video-edit.model' 4export * from './video-edit.model'
diff --git a/client/src/app/shared/shared-main/video/video.service.ts b/client/src/app/shared/shared-main/video/video.service.ts
index 7935569e7..9e3aa1e6a 100644
--- a/client/src/app/shared/shared-main/video/video.service.ts
+++ b/client/src/app/shared/shared-main/video/video.service.ts
@@ -1,8 +1,9 @@
1import { Observable } from 'rxjs' 1import { SortMeta } from 'primeng/api'
2import { catchError, map, switchMap } from 'rxjs/operators' 2import { from, Observable } from 'rxjs'
3import { catchError, concatMap, map, switchMap, toArray } from 'rxjs/operators'
3import { HttpClient, HttpParams, HttpRequest } from '@angular/common/http' 4import { HttpClient, HttpParams, HttpRequest } from '@angular/common/http'
4import { Injectable } from '@angular/core' 5import { Injectable } from '@angular/core'
5import { ComponentPaginationLight, RestExtractor, RestService, ServerService, UserService } from '@app/core' 6import { ComponentPaginationLight, RestExtractor, RestPagination, RestService, ServerService, UserService } from '@app/core'
6import { objectToFormData } from '@app/helpers' 7import { objectToFormData } from '@app/helpers'
7import { 8import {
8 BooleanBothQuery, 9 BooleanBothQuery,
@@ -31,8 +32,8 @@ import { VideoEdit } from './video-edit.model'
31import { Video } from './video.model' 32import { Video } from './video.model'
32 33
33export type CommonVideoParams = { 34export type CommonVideoParams = {
34 videoPagination: ComponentPaginationLight 35 videoPagination?: ComponentPaginationLight
35 sort: VideoSortField 36 sort: VideoSortField | SortMeta
36 filter?: VideoFilter 37 filter?: VideoFilter
37 categoryOneOf?: number[] 38 categoryOneOf?: number[]
38 languageOneOf?: string[] 39 languageOneOf?: string[]
@@ -200,6 +201,31 @@ export class VideoService {
200 ) 201 )
201 } 202 }
202 203
204 getAdminVideos (
205 parameters: Omit<CommonVideoParams, 'filter'> & { pagination: RestPagination, search?: string }
206 ): Observable<ResultList<Video>> {
207 const { pagination, search } = parameters
208
209 let params = new HttpParams()
210 params = this.buildCommonVideosParams({ params, ...parameters })
211
212 params = params.set('start', pagination.start.toString())
213 .set('count', pagination.count.toString())
214
215 if (search) {
216 params = this.buildAdminParamsFromSearch(search, params)
217 }
218
219 if (!params.has('filter')) params = params.set('filter', 'all')
220
221 return this.authHttp
222 .get<ResultList<Video>>(VideoService.BASE_VIDEO_URL, { params })
223 .pipe(
224 switchMap(res => this.extractVideos(res)),
225 catchError(err => this.restExtractor.handleError(err))
226 )
227 }
228
203 getVideos (parameters: CommonVideoParams): Observable<ResultList<Video>> { 229 getVideos (parameters: CommonVideoParams): Observable<ResultList<Video>> {
204 let params = new HttpParams() 230 let params = new HttpParams()
205 params = this.buildCommonVideosParams({ params, ...parameters }) 231 params = this.buildCommonVideosParams({ params, ...parameters })
@@ -284,13 +310,15 @@ export class VideoService {
284 ) 310 )
285 } 311 }
286 312
287 removeVideo (id: number) { 313 removeVideo (idArg: number | number[]) {
288 return this.authHttp 314 const ids = Array.isArray(idArg) ? idArg : [ idArg ]
289 .delete(VideoService.BASE_VIDEO_URL + id) 315
290 .pipe( 316 return from(ids)
291 map(this.restExtractor.extractDataBool), 317 .pipe(
292 catchError(err => this.restExtractor.handleError(err)) 318 concatMap(id => this.authHttp.delete(VideoService.BASE_VIDEO_URL + id)),
293 ) 319 toArray(),
320 catchError(err => this.restExtractor.handleError(err))
321 )
294 } 322 }
295 323
296 loadCompleteDescription (descriptionPath: string) { 324 loadCompleteDescription (descriptionPath: string) {
@@ -393,9 +421,23 @@ export class VideoService {
393 } 421 }
394 422
395 private buildCommonVideosParams (options: CommonVideoParams & { params: HttpParams }) { 423 private buildCommonVideosParams (options: CommonVideoParams & { params: HttpParams }) {
396 const { params, videoPagination, sort, filter, categoryOneOf, languageOneOf, skipCount, nsfwPolicy, isLive, nsfw } = options 424 const {
425 params,
426 videoPagination,
427 sort,
428 filter,
429 categoryOneOf,
430 languageOneOf,
431 skipCount,
432 nsfwPolicy,
433 isLive,
434 nsfw
435 } = options
436
437 const pagination = videoPagination
438 ? this.restService.componentToRestPagination(videoPagination)
439 : undefined
397 440
398 const pagination = this.restService.componentToRestPagination(videoPagination)
399 let newParams = this.restService.addRestGetParams(params, pagination, sort) 441 let newParams = this.restService.addRestGetParams(params, pagination, sort)
400 442
401 if (filter) newParams = newParams.set('filter', filter) 443 if (filter) newParams = newParams.set('filter', filter)
@@ -409,4 +451,19 @@ export class VideoService {
409 451
410 return newParams 452 return newParams
411 } 453 }
454
455 private buildAdminParamsFromSearch (search: string, params: HttpParams) {
456 const filters = this.restService.parseQueryStringFilter(search, {
457 filter: {
458 prefix: 'local:',
459 handler: v => {
460 if (v === 'true') return 'all-local'
461
462 return 'all'
463 }
464 }
465 })
466
467 return this.restService.addObjectParams(params, filters)
468 }
412} 469}
diff --git a/client/src/app/shared/shared-moderation/moderation.scss b/client/src/app/shared/shared-moderation/moderation.scss
index 815e2791f..eaf5a8250 100644
--- a/client/src/app/shared/shared-moderation/moderation.scss
+++ b/client/src/app/shared/shared-moderation/moderation.scss
@@ -40,14 +40,6 @@
40 } 40 }
41} 41}
42 42
43.screenratio {
44 @include block-ratio($selector: 'div, ::ng-deep iframe') {
45 width: 100% !important;
46 height: 100% !important;
47 left: 0;
48 };
49}
50
51.chip { 43.chip {
52 @include chip; 44 @include chip;
53} 45}
@@ -58,13 +50,6 @@ my-action-dropdown.show {
58 } 50 }
59} 51}
60 52
61.table-video-link {
62 @include disable-outline;
63
64 position: relative;
65 top: 3px;
66}
67
68.table-comment-link, 53.table-comment-link,
69.table-account-link { 54.table-account-link {
70 @include disable-outline; 55 @include disable-outline;
@@ -81,68 +66,6 @@ my-action-dropdown.show {
81 flex-direction: column; 66 flex-direction: column;
82} 67}
83 68
84.table-video {
85 display: inline-flex;
86
87 .table-video-image {
88 $image-height: 45px;
89
90 @include miniature-thumbnail;
91 @include margin-right(0.5rem);
92
93 height: $image-height;
94 width: #{math.div(16, 9) * $image-height};
95 border-radius: 2px;
96 border: 0;
97 background: transparent;
98 display: inline-flex;
99 justify-content: center;
100 position: relative;
101
102 img {
103 height: 100%;
104 width: 100%;
105 border-radius: 2px;
106 }
107
108 span {
109 color: pvar(--inputPlaceholderColor);
110 }
111
112 .table-video-image-label {
113 @include static-thumbnail-overlay;
114 position: absolute;
115 border-radius: 3px;
116 font-size: 10px;
117 padding: 0 3px;
118 line-height: 1.3;
119 bottom: 2px;
120 right: 2px;
121 }
122 }
123
124 .table-video-text {
125 display: inline-flex;
126 flex-direction: column;
127 justify-content: center;
128 font-size: 90%;
129 color: pvar(--mainForegroundColor);
130 line-height: 1rem;
131
132 div .glyphicon {
133 @include margin-left(0.1rem);
134
135 font-size: 80%;
136 color: #808080;
137 }
138
139 div + div {
140 color: var(--greyForegroundColor);
141 font-size: 11px;
142 }
143 }
144}
145
146my-abuse-details { 69my-abuse-details {
147 width: 100%; 70 width: 100%;
148} 71}
diff --git a/client/src/app/shared/shared-moderation/report-modals/report.component.scss b/client/src/app/shared/shared-moderation/report-modals/report.component.scss
index 06e50ac2d..76ec0a6ed 100644
--- a/client/src/app/shared/shared-moderation/report-modals/report.component.scss
+++ b/client/src/app/shared/shared-moderation/report-modals/report.component.scss
@@ -19,9 +19,3 @@ textarea {
19 @include margin-left(10px); 19 @include margin-left(10px);
20 } 20 }
21} 21}
22
23.screenratio {
24 @include block-ratio($selector: 'div, ::ng-deep iframe') {
25 left: 0;
26 };
27}
diff --git a/client/src/app/shared/shared-moderation/report-modals/video-report.component.html b/client/src/app/shared/shared-moderation/report-modals/video-report.component.html
index 1aae64bff..afac108fc 100644
--- a/client/src/app/shared/shared-moderation/report-modals/video-report.component.html
+++ b/client/src/app/shared/shared-moderation/report-modals/video-report.component.html
@@ -35,9 +35,7 @@
35 <div class="col-7"> 35 <div class="col-7">
36 <div class="row justify-content-center"> 36 <div class="row justify-content-center">
37 <div class="col-12 col-lg-9 mb-2"> 37 <div class="col-12 col-lg-9 mb-2">
38 <div class="screenratio"> 38 <my-embed [video]="video"></my-embed>
39 <div [innerHTML]="embedHtml"></div>
40 </div>
41 </div> 39 </div>
42 </div> 40 </div>
43 41
diff --git a/client/src/app/shared/shared-moderation/report-modals/video-report.component.ts b/client/src/app/shared/shared-moderation/report-modals/video-report.component.ts
index 278d60ac6..38dd92910 100644
--- a/client/src/app/shared/shared-moderation/report-modals/video-report.component.ts
+++ b/client/src/app/shared/shared-moderation/report-modals/video-report.component.ts
@@ -1,13 +1,11 @@
1import { mapValues, pickBy } from 'lodash-es' 1import { mapValues, pickBy } from 'lodash-es'
2import { buildVideoOrPlaylistEmbed } from 'src/assets/player/utils'
3import { Component, Input, OnInit, ViewChild } from '@angular/core' 2import { Component, Input, OnInit, ViewChild } from '@angular/core'
4import { DomSanitizer, SafeHtml } from '@angular/platform-browser' 3import { DomSanitizer } from '@angular/platform-browser'
5import { Notifier } from '@app/core' 4import { Notifier } from '@app/core'
6import { ABUSE_REASON_VALIDATOR } from '@app/shared/form-validators/abuse-validators' 5import { ABUSE_REASON_VALIDATOR } from '@app/shared/form-validators/abuse-validators'
7import { FormReactive, FormValidatorService } from '@app/shared/shared-forms' 6import { FormReactive, FormValidatorService } from '@app/shared/shared-forms'
8import { NgbModal } from '@ng-bootstrap/ng-bootstrap' 7import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
9import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref' 8import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref'
10import { decorateVideoLink } from '@shared/core-utils'
11import { abusePredefinedReasonsMap } from '@shared/core-utils/abuse' 9import { abusePredefinedReasonsMap } from '@shared/core-utils/abuse'
12import { AbusePredefinedReasonsString } from '@shared/models' 10import { AbusePredefinedReasonsString } from '@shared/models'
13import { Video } from '../../shared-main' 11import { Video } from '../../shared-main'
@@ -25,7 +23,6 @@ export class VideoReportComponent extends FormReactive implements OnInit {
25 23
26 error: string = null 24 error: string = null
27 predefinedReasons: { id: AbusePredefinedReasonsString, label: string, description?: string, help?: string }[] = [] 25 predefinedReasons: { id: AbusePredefinedReasonsString, label: string, description?: string, help?: string }[] = []
28 embedHtml: SafeHtml
29 26
30 private openedModal: NgbModalRef 27 private openedModal: NgbModalRef
31 28
@@ -55,20 +52,6 @@ export class VideoReportComponent extends FormReactive implements OnInit {
55 return this.form.get('timestamp').value 52 return this.form.get('timestamp').value
56 } 53 }
57 54
58 getVideoEmbed () {
59 return this.sanitizer.bypassSecurityTrustHtml(
60 buildVideoOrPlaylistEmbed(
61 decorateVideoLink({
62 url: this.video.embedUrl,
63 title: false,
64 warningTitle: false
65 }),
66
67 this.video.name
68 )
69 )
70 }
71
72 ngOnInit () { 55 ngOnInit () {
73 this.buildForm({ 56 this.buildForm({
74 reason: ABUSE_REASON_VALIDATOR, 57 reason: ABUSE_REASON_VALIDATOR,
@@ -82,8 +65,6 @@ export class VideoReportComponent extends FormReactive implements OnInit {
82 }) 65 })
83 66
84 this.predefinedReasons = this.abuseService.getPrefefinedReasons('video') 67 this.predefinedReasons = this.abuseService.getPrefefinedReasons('video')
85
86 this.embedHtml = this.getVideoEmbed()
87 } 68 }
88 69
89 show () { 70 show () {
diff --git a/client/src/app/shared/shared-tables/index.ts b/client/src/app/shared/shared-tables/index.ts
new file mode 100644
index 000000000..e7b593257
--- /dev/null
+++ b/client/src/app/shared/shared-tables/index.ts
@@ -0,0 +1,3 @@
1export * from './table-expander-icon.component'
2export * from './video-cell.component'
3export * from './shared-tables.module'
diff --git a/client/src/app/shared/shared-tables/shared-tables.module.ts b/client/src/app/shared/shared-tables/shared-tables.module.ts
new file mode 100644
index 000000000..c528365a0
--- /dev/null
+++ b/client/src/app/shared/shared-tables/shared-tables.module.ts
@@ -0,0 +1,25 @@
1
2import { NgModule } from '@angular/core'
3import { SharedMainModule } from '../shared-main/shared-main.module'
4import { TableExpanderIconComponent } from './table-expander-icon.component'
5import { VideoCellComponent } from './video-cell.component'
6
7@NgModule({
8 imports: [
9 SharedMainModule
10 ],
11
12 declarations: [
13 VideoCellComponent,
14 TableExpanderIconComponent
15 ],
16
17 exports: [
18 VideoCellComponent,
19 TableExpanderIconComponent
20 ],
21
22 providers: [
23 ]
24})
25export class SharedTablesModule { }
diff --git a/client/src/app/shared/shared-tables/table-expander-icon.component.ts b/client/src/app/shared/shared-tables/table-expander-icon.component.ts
new file mode 100644
index 000000000..3756b475a
--- /dev/null
+++ b/client/src/app/shared/shared-tables/table-expander-icon.component.ts
@@ -0,0 +1,12 @@
1import { Component, Input } from '@angular/core'
2
3@Component({
4 selector: 'my-table-expander-icon',
5 template: `
6<span class="expander">
7 <i [ngClass]="expanded ? 'glyphicon glyphicon-menu-down' : 'glyphicon glyphicon-menu-right'"></i>
8</span>`
9})
10export class TableExpanderIconComponent {
11 @Input() expanded: boolean
12}
diff --git a/client/src/app/shared/shared-tables/video-cell.component.html b/client/src/app/shared/shared-tables/video-cell.component.html
new file mode 100644
index 000000000..fb7d852ab
--- /dev/null
+++ b/client/src/app/shared/shared-tables/video-cell.component.html
@@ -0,0 +1,19 @@
1<a [href]="getVideoUrl()" class="table-video-link" [title]="video.name" target="_blank" rel="noopener noreferrer">
2 <div class="table-video">
3 <div class="table-video-image">
4 <img [src]="video.thumbnailPath">
5
6 <ng-content select="[image]"></ng-content>
7 </div>
8
9 <div class="table-video-text">
10 <div>
11 <ng-content select="[name]"></ng-content>
12
13 {{ video.name }}
14 </div>
15
16 <div class="text-muted">by {{ video.channel?.displayName }} on {{ video.channel?.host }} </div>
17 </div>
18 </div>
19</a>
diff --git a/client/src/app/shared/shared-tables/video-cell.component.scss b/client/src/app/shared/shared-tables/video-cell.component.scss
new file mode 100644
index 000000000..7efb61502
--- /dev/null
+++ b/client/src/app/shared/shared-tables/video-cell.component.scss
@@ -0,0 +1,74 @@
1@use 'sass:math';
2@use '_mixins' as *;
3@use '_variables' as *;
4@use '_miniature' as *;
5
6.table-video-link {
7 @include disable-outline;
8
9 position: relative;
10 top: 3px;
11}
12
13.table-video {
14 display: inline-flex;
15
16 .table-video-image {
17 $image-height: 45px;
18
19 @include miniature-thumbnail;
20 @include margin-right(0.5rem);
21
22 height: $image-height;
23 width: #{math.div(16, 9) * $image-height};
24 border-radius: 2px;
25 border: 0;
26 background: transparent;
27 display: inline-flex;
28 justify-content: center;
29 position: relative;
30
31 img {
32 height: 100%;
33 width: 100%;
34 border-radius: 2px;
35 }
36
37 span {
38 color: pvar(--inputPlaceholderColor);
39 }
40
41 .table-video-image-label {
42 @include static-thumbnail-overlay;
43
44 position: absolute;
45 border-radius: 3px;
46 font-size: 10px;
47 padding: 0 3px;
48 line-height: 1.3;
49 bottom: 2px;
50 right: 2px;
51 }
52 }
53
54 .table-video-text {
55 display: inline-flex;
56 flex-direction: column;
57 justify-content: center;
58 font-size: 90%;
59 color: pvar(--mainForegroundColor);
60 line-height: 1rem;
61
62 div .glyphicon {
63 @include margin-left(0.1rem);
64
65 font-size: 80%;
66 color: #808080;
67 }
68
69 div + div {
70 color: var(--greyForegroundColor);
71 font-size: 11px;
72 }
73 }
74}
diff --git a/client/src/app/shared/shared-tables/video-cell.component.ts b/client/src/app/shared/shared-tables/video-cell.component.ts
new file mode 100644
index 000000000..62984180d
--- /dev/null
+++ b/client/src/app/shared/shared-tables/video-cell.component.ts
@@ -0,0 +1,15 @@
1import { Component, Input } from '@angular/core'
2import { Video } from '@app/shared/shared-main'
3
4@Component({
5 selector: 'my-video-cell',
6 styleUrls: [ 'video-cell.component.scss' ],
7 templateUrl: 'video-cell.component.html'
8})
9export class VideoCellComponent {
10 @Input() video: Video
11
12 getVideoUrl () {
13 return Video.buildWatchUrl(this.video)
14 }
15}