aboutsummaryrefslogtreecommitdiffhomepage
path: root/client/src/app/+admin/overview
diff options
context:
space:
mode:
authorChocobozzz <me@florianbigard.com>2022-02-28 16:27:25 +0100
committerChocobozzz <me@florianbigard.com>2022-02-28 16:27:25 +0100
commit5a51ecc2172282786dab47bd874026621554ba6d (patch)
tree129a6bc40d2f048fd73362e427b75974f040ac5f /client/src/app/+admin/overview
parentf1c70a8666e53414f4e604290d35d26ae725b691 (diff)
downloadPeerTube-5a51ecc2172282786dab47bd874026621554ba6d.tar.gz
PeerTube-5a51ecc2172282786dab47bd874026621554ba6d.tar.zst
PeerTube-5a51ecc2172282786dab47bd874026621554ba6d.zip
Move admin comments list in overviews menu
Diffstat (limited to 'client/src/app/+admin/overview')
-rw-r--r--client/src/app/+admin/overview/comments/index.ts2
-rw-r--r--client/src/app/+admin/overview/comments/video-comment-list.component.html111
-rw-r--r--client/src/app/+admin/overview/comments/video-comment-list.component.scss52
-rw-r--r--client/src/app/+admin/overview/comments/video-comment-list.component.ts186
-rw-r--r--client/src/app/+admin/overview/comments/video-comment.routes.ts30
-rw-r--r--client/src/app/+admin/overview/index.ts1
-rw-r--r--client/src/app/+admin/overview/overview.routes.ts10
-rw-r--r--client/src/app/+admin/overview/users/users.routes.ts2
-rw-r--r--client/src/app/+admin/overview/videos/video.routes.ts2
9 files changed, 390 insertions, 6 deletions
diff --git a/client/src/app/+admin/overview/comments/index.ts b/client/src/app/+admin/overview/comments/index.ts
new file mode 100644
index 000000000..c487f7a81
--- /dev/null
+++ b/client/src/app/+admin/overview/comments/index.ts
@@ -0,0 +1,2 @@
1export * from './video-comment-list.component'
2export * from './video-comment.routes'
diff --git a/client/src/app/+admin/overview/comments/video-comment-list.component.html b/client/src/app/+admin/overview/comments/video-comment-list.component.html
new file mode 100644
index 000000000..0dbbbe1cc
--- /dev/null
+++ b/client/src/app/+admin/overview/comments/video-comment-list.component.html
@@ -0,0 +1,111 @@
1<h1>
2 <my-global-icon iconName="message-circle" aria-hidden="true"></my-global-icon>
3 <ng-container i18n>Video comments</ng-container>
4
5 <my-feed [syndicationItems]="syndicationItems"></my-feed>
6</h1>
7
8<em i18n>This view also shows comments from muted accounts.</em>
9
10<p-table
11 [value]="comments" [paginator]="totalRecords > 0" [totalRecords]="totalRecords" [rows]="rowsPerPage" [rowsPerPageOptions]="rowsPerPageOptions"
12 [sortField]="sort.field" [sortOrder]="sort.order" dataKey="id"
13 [lazy]="true" (onLazyLoad)="loadLazy($event)" [lazyLoadOnInit]="false" [selectionPageOnly]="true"
14 [showCurrentPageReport]="true" i18n-currentPageReportTemplate
15 currentPageReportTemplate="Showing {{'{first}'}} to {{'{last}'}} of {{'{totalRecords}'}} comments"
16 [expandedRowKeys]="expandedRows" [(selection)]="selectedComments"
17>
18 <ng-template pTemplate="caption">
19 <div class="caption">
20 <div>
21 <my-action-dropdown
22 *ngIf="isInSelectionMode()" i18n-label label="Batch actions" theme="orange"
23 [actions]="bulkCommentActions" [entry]="selectedComments"
24 >
25 </my-action-dropdown>
26 </div>
27
28 <div class="ml-auto">
29 <my-advanced-input-filter [filters]="inputFilters" (search)="onSearch($event)"></my-advanced-input-filter>
30 </div>
31 </div>
32 </ng-template>
33
34 <ng-template pTemplate="header">
35 <tr>
36 <th style="width: 40px;">
37 <p-tableHeaderCheckbox ariaLabel="Select all rows" i18n-ariaLabel></p-tableHeaderCheckbox>
38 </th>
39 <th style="width: 40px;"></th>
40 <th style="width: 150px;"></th>
41 <th style="width: 300px;" i18n>Account</th>
42 <th style="width: 300px;" i18n>Video</th>
43 <th i18n>Comment</th>
44 <th style="width: 150px;" i18n pSortableColumn="createdAt">Date <p-sortIcon field="createdAt"></p-sortIcon></th>
45 </tr>
46 </ng-template>
47
48 <ng-template pTemplate="body" let-videoComment let-expanded="expanded">
49 <tr [pSelectableRow]="videoComment">
50
51 <td class="checkbox-cell">
52 <p-tableCheckbox [value]="videoComment" ariaLabel="Select this row" i18n-ariaLabel></p-tableCheckbox>
53 </td>
54
55 <td class="expand-cell" [pRowToggler]="videoComment">
56 <my-table-expander-icon i18n-ngbTooltip ngbTooltip="See full comment" [expanded]="expanded"></my-table-expander-icon>
57 </td>
58
59 <td class="action-cell">
60 <my-action-dropdown
61 [ngClass]="{ 'show': expanded }" placement="bottom-right" container="body"
62 i18n-label label="Actions" [actions]="videoCommentActions" [entry]="videoComment"
63 ></my-action-dropdown>
64 </td>
65
66 <td>
67 <a [href]="videoComment.account.localUrl" i18n-title title="Open account in a new tab" target="_blank" rel="noopener noreferrer">
68 <div class="chip two-lines">
69 <my-actor-avatar [account]="videoComment.account" size="32"></my-actor-avatar>
70 <div>
71 {{ videoComment.account.displayName }}
72 <span>{{ videoComment.by }}</span>
73 </div>
74 </div>
75 </a>
76 </td>
77
78 <td class="video">
79 <em i18n>Commented video</em>
80
81 <a [href]="videoComment.localUrl" target="_blank" rel="noopener noreferrer">{{ videoComment.video.name }}</a>
82 </td>
83
84 <td class="comment-html c-hand" [pRowToggler]="videoComment">
85 <div [innerHTML]="videoComment.textHtml"></div>
86 </td>
87
88 <td class="c-hand" [pRowToggler]="videoComment">{{ videoComment.createdAt | date: 'short' }}</td>
89 </tr>
90 </ng-template>
91
92 <ng-template pTemplate="rowexpansion" let-videoComment>
93 <tr>
94 <td class="expand-cell" colspan="5">
95 <div [innerHTML]="videoComment.textHtml"></div>
96 </td>
97 </tr>
98 </ng-template>
99
100 <ng-template pTemplate="emptymessage">
101 <tr>
102 <td colspan="7">
103 <div class="no-results">
104 <ng-container *ngIf="search" i18n>No comments found matching current filters.</ng-container>
105 <ng-container *ngIf="!search" i18n>No comments found.</ng-container>
106 </div>
107 </td>
108 </tr>
109 </ng-template>
110</p-table>
111
diff --git a/client/src/app/+admin/overview/comments/video-comment-list.component.scss b/client/src/app/+admin/overview/comments/video-comment-list.component.scss
new file mode 100644
index 000000000..3cf7b8db6
--- /dev/null
+++ b/client/src/app/+admin/overview/comments/video-comment-list.component.scss
@@ -0,0 +1,52 @@
1@use '_mixins' as *;
2@use '_variables' as *;
3
4my-feed {
5 @include margin-left(5px);
6
7 display: inline-block;
8 width: 15px;
9}
10
11my-global-icon {
12 width: 24px;
13 height: 24px;
14}
15
16.video {
17 display: flex;
18 flex-direction: column;
19
20 em {
21 font-size: 11px;
22 }
23
24 a {
25 @include ellipsis;
26
27 color: pvar(--mainForegroundColor);
28 }
29}
30
31.comment-html {
32 ::ng-deep {
33 > div {
34 max-height: 22px;
35 }
36
37 div,
38 p {
39 @include ellipsis;
40 }
41
42 p {
43 margin: 0;
44 }
45 }
46}
47
48@media screen and (max-width: $primeng-breakpoint) {
49 .video {
50 align-items: flex-start !important;
51 }
52}
diff --git a/client/src/app/+admin/overview/comments/video-comment-list.component.ts b/client/src/app/+admin/overview/comments/video-comment-list.component.ts
new file mode 100644
index 000000000..25fe65133
--- /dev/null
+++ b/client/src/app/+admin/overview/comments/video-comment-list.component.ts
@@ -0,0 +1,186 @@
1import { SortMeta } from 'primeng/api'
2import { Component, OnInit } from '@angular/core'
3import { ActivatedRoute, Router } from '@angular/router'
4import { AuthService, ConfirmService, MarkdownService, Notifier, RestPagination, RestTable } from '@app/core'
5import { AdvancedInputFilter } from '@app/shared/shared-forms'
6import { DropdownAction } from '@app/shared/shared-main'
7import { BulkService } from '@app/shared/shared-moderation'
8import { VideoCommentAdmin, VideoCommentService } from '@app/shared/shared-video-comment'
9import { FeedFormat, UserRight } from '@shared/models'
10
11@Component({
12 selector: 'my-video-comment-list',
13 templateUrl: './video-comment-list.component.html',
14 styleUrls: [ '../../../shared/shared-moderation/moderation.scss', './video-comment-list.component.scss' ]
15})
16export class VideoCommentListComponent extends RestTable implements OnInit {
17 comments: VideoCommentAdmin[]
18 totalRecords = 0
19 sort: SortMeta = { field: 'createdAt', order: -1 }
20 pagination: RestPagination = { count: this.rowsPerPage, start: 0 }
21
22 videoCommentActions: DropdownAction<VideoCommentAdmin>[][] = []
23
24 syndicationItems = [
25 {
26 format: FeedFormat.RSS,
27 label: 'media rss 2.0',
28 url: VideoCommentService.BASE_FEEDS_URL + FeedFormat.RSS.toLowerCase()
29 },
30 {
31 format: FeedFormat.ATOM,
32 label: 'atom 1.0',
33 url: VideoCommentService.BASE_FEEDS_URL + FeedFormat.ATOM.toLowerCase()
34 },
35 {
36 format: FeedFormat.JSON,
37 label: 'json 1.0',
38 url: VideoCommentService.BASE_FEEDS_URL + FeedFormat.JSON.toLowerCase()
39 }
40 ]
41
42 selectedComments: VideoCommentAdmin[] = []
43 bulkCommentActions: DropdownAction<VideoCommentAdmin[]>[] = []
44
45 inputFilters: AdvancedInputFilter[] = [
46 {
47 title: $localize`Advanced filters`,
48 children: [
49 {
50 value: 'local:true',
51 label: $localize`Local comments`
52 },
53 {
54 value: 'local:false',
55 label: $localize`Remote comments`
56 }
57 ]
58 }
59 ]
60
61 get authUser () {
62 return this.auth.getUser()
63 }
64
65 constructor (
66 protected router: Router,
67 protected route: ActivatedRoute,
68 private auth: AuthService,
69 private notifier: Notifier,
70 private confirmService: ConfirmService,
71 private videoCommentService: VideoCommentService,
72 private markdownRenderer: MarkdownService,
73 private bulkService: BulkService
74 ) {
75 super()
76
77 this.videoCommentActions = [
78 [
79 {
80 label: $localize`Delete this comment`,
81 handler: comment => this.deleteComment(comment),
82 isDisplayed: () => this.authUser.hasRight(UserRight.REMOVE_ANY_VIDEO_COMMENT)
83 },
84
85 {
86 label: $localize`Delete all comments of this account`,
87 description: $localize`Comments are deleted after a few minutes`,
88 handler: comment => this.deleteUserComments(comment),
89 isDisplayed: () => this.authUser.hasRight(UserRight.REMOVE_ANY_VIDEO_COMMENT)
90 }
91 ]
92 ]
93 }
94
95 ngOnInit () {
96 this.initialize()
97
98 this.bulkCommentActions = [
99 {
100 label: $localize`Delete`,
101 handler: comments => this.removeComments(comments),
102 isDisplayed: () => this.authUser.hasRight(UserRight.REMOVE_ANY_VIDEO_COMMENT),
103 iconName: 'delete'
104 }
105 ]
106 }
107
108 getIdentifier () {
109 return 'VideoCommentListComponent'
110 }
111
112 toHtml (text: string) {
113 return this.markdownRenderer.textMarkdownToHTML(text, true, true)
114 }
115
116 isInSelectionMode () {
117 return this.selectedComments.length !== 0
118 }
119
120 protected reloadData () {
121 this.videoCommentService.getAdminVideoComments({
122 pagination: this.pagination,
123 sort: this.sort,
124 search: this.search
125 }).subscribe({
126 next: async resultList => {
127 this.totalRecords = resultList.total
128
129 this.comments = []
130
131 for (const c of resultList.data) {
132 this.comments.push(
133 new VideoCommentAdmin(c, await this.toHtml(c.text))
134 )
135 }
136 },
137
138 error: err => this.notifier.error(err.message)
139 })
140 }
141
142 private removeComments (comments: VideoCommentAdmin[]) {
143 const commentArgs = comments.map(c => ({ videoId: c.video.id, commentId: c.id }))
144
145 this.videoCommentService.deleteVideoComments(commentArgs)
146 .subscribe({
147 next: () => {
148 this.notifier.success($localize`${commentArgs.length} comments deleted.`)
149 this.reloadData()
150 },
151
152 error: err => this.notifier.error(err.message),
153
154 complete: () => this.selectedComments = []
155 })
156 }
157
158 private deleteComment (comment: VideoCommentAdmin) {
159 this.videoCommentService.deleteVideoComment(comment.video.id, comment.id)
160 .subscribe({
161 next: () => this.reloadData(),
162
163 error: err => this.notifier.error(err.message)
164 })
165 }
166
167 private async deleteUserComments (comment: VideoCommentAdmin) {
168 const message = $localize`Do you really want to delete all comments of ${comment.by}?`
169 const res = await this.confirmService.confirm(message, $localize`Delete`)
170 if (res === false) return
171
172 const options = {
173 accountName: comment.by,
174 scope: 'instance' as 'instance'
175 }
176
177 this.bulkService.removeCommentsOf(options)
178 .subscribe({
179 next: () => {
180 this.notifier.success($localize`Comments of ${options.accountName} will be deleted in a few minutes`)
181 },
182
183 error: err => this.notifier.error(err.message)
184 })
185 }
186}
diff --git a/client/src/app/+admin/overview/comments/video-comment.routes.ts b/client/src/app/+admin/overview/comments/video-comment.routes.ts
new file mode 100644
index 000000000..f0bd440ad
--- /dev/null
+++ b/client/src/app/+admin/overview/comments/video-comment.routes.ts
@@ -0,0 +1,30 @@
1import { Routes } from '@angular/router'
2import { UserRightGuard } from '@app/core'
3import { UserRight } from '@shared/models'
4import { VideoCommentListComponent } from './video-comment-list.component'
5
6export const commentRoutes: Routes = [
7 {
8 path: 'comments',
9 canActivate: [ UserRightGuard ],
10 data: {
11 userRight: UserRight.SEE_ALL_COMMENTS
12 },
13 children: [
14 {
15 path: '',
16 redirectTo: 'list',
17 pathMatch: 'full'
18 },
19 {
20 path: 'list',
21 component: VideoCommentListComponent,
22 data: {
23 meta: {
24 title: $localize`Comments list`
25 }
26 }
27 }
28 ]
29 }
30]
diff --git a/client/src/app/+admin/overview/index.ts b/client/src/app/+admin/overview/index.ts
index a9c46893f..111360734 100644
--- a/client/src/app/+admin/overview/index.ts
+++ b/client/src/app/+admin/overview/index.ts
@@ -1,3 +1,4 @@
1export * from './comments'
1export * from './users' 2export * from './users'
2export * from './videos' 3export * from './videos'
3export * from './overview.routes' 4export * from './overview.routes'
diff --git a/client/src/app/+admin/overview/overview.routes.ts b/client/src/app/+admin/overview/overview.routes.ts
index 1e6686d16..72d6835d7 100644
--- a/client/src/app/+admin/overview/overview.routes.ts
+++ b/client/src/app/+admin/overview/overview.routes.ts
@@ -1,8 +1,10 @@
1import { Routes } from '@angular/router' 1import { Routes } from '@angular/router'
2import { UsersRoutes } from './users' 2import { commentRoutes } from './comments'
3import { VideosRoutes } from './videos' 3import { usersRoutes } from './users'
4import { videosRoutes } from './videos'
4 5
5export const OverviewRoutes: Routes = [ 6export const OverviewRoutes: Routes = [
6 ...UsersRoutes, 7 ...commentRoutes,
7 ...VideosRoutes 8 ...usersRoutes,
9 ...videosRoutes
8] 10]
diff --git a/client/src/app/+admin/overview/users/users.routes.ts b/client/src/app/+admin/overview/users/users.routes.ts
index 8b63f5bc7..c9724e5fb 100644
--- a/client/src/app/+admin/overview/users/users.routes.ts
+++ b/client/src/app/+admin/overview/users/users.routes.ts
@@ -4,7 +4,7 @@ import { UserRight } from '@shared/models'
4import { UserCreateComponent, UserUpdateComponent } from './user-edit' 4import { UserCreateComponent, UserUpdateComponent } from './user-edit'
5import { UserListComponent } from './user-list' 5import { UserListComponent } from './user-list'
6 6
7export const UsersRoutes: Routes = [ 7export const usersRoutes: Routes = [
8 { 8 {
9 path: 'users', 9 path: 'users',
10 canActivate: [ UserRightGuard ], 10 canActivate: [ UserRightGuard ],
diff --git a/client/src/app/+admin/overview/videos/video.routes.ts b/client/src/app/+admin/overview/videos/video.routes.ts
index 984df7b82..01cb5b497 100644
--- a/client/src/app/+admin/overview/videos/video.routes.ts
+++ b/client/src/app/+admin/overview/videos/video.routes.ts
@@ -3,7 +3,7 @@ import { UserRightGuard } from '@app/core'
3import { UserRight } from '@shared/models' 3import { UserRight } from '@shared/models'
4import { VideoListComponent } from './video-list.component' 4import { VideoListComponent } from './video-list.component'
5 5
6export const VideosRoutes: Routes = [ 6export const videosRoutes: Routes = [
7 { 7 {
8 path: 'videos', 8 path: 'videos',
9 canActivate: [ UserRightGuard ], 9 canActivate: [ UserRightGuard ],