aboutsummaryrefslogtreecommitdiffhomepage
path: root/client/src/app
diff options
context:
space:
mode:
authorChocobozzz <me@florianbigard.com>2020-11-16 11:55:17 +0100
committerChocobozzz <me@florianbigard.com>2020-11-16 13:48:58 +0100
commitf1273314593a4a7dc7ec9594ce0c6c3ae8f62b34 (patch)
treecf1f3949e64a24a820833950d7b2bbf9ccd40013 /client/src/app
parent0f8d00e3144060270d7fe603865fccaf18649c47 (diff)
downloadPeerTube-f1273314593a4a7dc7ec9594ce0c6c3ae8f62b34.tar.gz
PeerTube-f1273314593a4a7dc7ec9594ce0c6c3ae8f62b34.tar.zst
PeerTube-f1273314593a4a7dc7ec9594ce0c6c3ae8f62b34.zip
Add admin view to manage comments
Diffstat (limited to 'client/src/app')
-rw-r--r--client/src/app/+admin/moderation/video-block-list/video-block-list.component.scss8
-rw-r--r--client/src/app/+admin/moderation/video-comment-list/video-comment-list.component.html36
-rw-r--r--client/src/app/+admin/moderation/video-comment-list/video-comment-list.component.scss51
-rw-r--r--client/src/app/+admin/moderation/video-comment-list/video-comment-list.component.ts86
-rw-r--r--client/src/app/shared/shared-main/feeds/feed.component.html2
-rw-r--r--client/src/app/shared/shared-main/feeds/feed.component.scss2
-rw-r--r--client/src/app/shared/shared-video-comment/video-comment.model.ts11
-rw-r--r--client/src/app/shared/shared-video-comment/video-comment.service.ts7
8 files changed, 160 insertions, 43 deletions
diff --git a/client/src/app/+admin/moderation/video-block-list/video-block-list.component.scss b/client/src/app/+admin/moderation/video-block-list/video-block-list.component.scss
index c92d1c39c..0e34150c1 100644
--- a/client/src/app/+admin/moderation/video-block-list/video-block-list.component.scss
+++ b/client/src/app/+admin/moderation/video-block-list/video-block-list.component.scss
@@ -1,12 +1,8 @@
1@import 'mixins'; 1@import 'mixins';
2 2
3my-global-icon { 3my-global-icon {
4 @include apply-svg-color(#7d7d7d); 4 width: 24px;
5 5 height: 24px;
6 width: 12px;
7 height: 12px;
8 position: relative;
9 top: -1px;
10} 6}
11 7
12.input-group { 8.input-group {
diff --git a/client/src/app/+admin/moderation/video-comment-list/video-comment-list.component.html b/client/src/app/+admin/moderation/video-comment-list/video-comment-list.component.html
index b4f66a75f..45c5fe28f 100644
--- a/client/src/app/+admin/moderation/video-comment-list/video-comment-list.component.html
+++ b/client/src/app/+admin/moderation/video-comment-list/video-comment-list.component.html
@@ -1,9 +1,11 @@
1<h1> 1<h1>
2 <my-global-icon iconName="cross" aria-hidden="true"></my-global-icon> 2 <my-global-icon iconName="message-circle" aria-hidden="true"></my-global-icon>
3 <ng-container i18n>Video comments</ng-container> 3 <ng-container i18n>Video comments</ng-container>
4
5 <my-feed [syndicationItems]="syndicationItems"></my-feed>
4</h1> 6</h1>
5 7
6this view does show comments from muted accounts so you can delete them 8<em>This view also shows comments from muted accounts.</em>
7 9
8<p-table 10<p-table
9 [value]="comments" [lazy]="true" [paginator]="totalRecords > 0" [totalRecords]="totalRecords" [rows]="rowsPerPage" [rowsPerPageOptions]="rowsPerPageOptions" 11 [value]="comments" [lazy]="true" [paginator]="totalRecords > 0" [totalRecords]="totalRecords" [rows]="rowsPerPage" [rowsPerPageOptions]="rowsPerPageOptions"
@@ -29,7 +31,7 @@ this view does show comments from muted accounts so you can delete them
29 </div> 31 </div>
30 <input 32 <input
31 type="text" name="table-filter" id="table-filter" i18n-placeholder placeholder="Filter..." 33 type="text" name="table-filter" id="table-filter" i18n-placeholder placeholder="Filter..."
32 (keyup)="onSearch($event)" 34 (keyup)="onInputSearch($event)"
33 > 35 >
34 <a class="glyphicon glyphicon-remove-sign form-control-feedback form-control-clear" (click)="resetTableFilter()"></a> 36 <a class="glyphicon glyphicon-remove-sign form-control-feedback form-control-clear" (click)="resetTableFilter()"></a>
35 <span class="sr-only" i18n>Clear filters</span> 37 <span class="sr-only" i18n>Clear filters</span>
@@ -41,9 +43,9 @@ this view does show comments from muted accounts so you can delete them
41 <ng-template pTemplate="header"> 43 <ng-template pTemplate="header">
42 <tr> 44 <tr>
43 <th style="width: 40px"></th> 45 <th style="width: 40px"></th>
44 <th style="width: 100px;" i18n>Account</th> 46 <th style="width: 300px" i18n>Account</th>
45 <th style="width: 100px;" i18n>Video</th> 47 <th style="width: 300px" i18n>Video</th>
46 <th style="width: 100px;" i18n>Comment</th> 48 <th i18n>Comment</th>
47 <th style="width: 150px;" i18n pSortableColumn="createdAt">Date <p-sortIcon field="createdAt"></p-sortIcon></th> 49 <th style="width: 150px;" i18n pSortableColumn="createdAt">Date <p-sortIcon field="createdAt"></p-sortIcon></th>
48 <th style="width: 150px;"></th> 50 <th style="width: 150px;"></th>
49 </tr> 51 </tr>
@@ -58,14 +60,28 @@ this view does show comments from muted accounts so you can delete them
58 </td> 60 </td>
59 61
60 <td> 62 <td>
61 {{ videoComment.by }} 63 <a [href]="videoComment.account.localUrl" i18n-title title="Open account in a new tab" target="_blank" rel="noopener noreferrer">
64 <div class="chip two-lines">
65 <img
66 class="avatar"
67 [src]="videoComment.accountAvatarUrl"
68 alt=""
69 >
70 <div>
71 {{ videoComment.account.displayName }}
72 <span>{{ videoComment.by }}</span>
73 </div>
74 </div>
75 </a>
62 </td> 76 </td>
63 77
64 <td> 78 <td class="video">
65 {{ videoComment.video.name }} 79 <em i18n>Commented video</em>
80
81 <a [href]="videoComment.localUrl" target="_blank" rel="noopener noreferrer">{{ videoComment.video.name }}</a>
66 </td> 82 </td>
67 83
68 <td> 84 <td class="comment-html">
69 <div [innerHTML]="videoComment.textHtml"></div> 85 <div [innerHTML]="videoComment.textHtml"></div>
70 </td> 86 </td>
71 87
diff --git a/client/src/app/+admin/moderation/video-comment-list/video-comment-list.component.scss b/client/src/app/+admin/moderation/video-comment-list/video-comment-list.component.scss
index c92d1c39c..b3746b0c5 100644
--- a/client/src/app/+admin/moderation/video-comment-list/video-comment-list.component.scss
+++ b/client/src/app/+admin/moderation/video-comment-list/video-comment-list.component.scss
@@ -1,12 +1,22 @@
1@import 'mixins'; 1@import 'mixins';
2 2
3my-global-icon { 3h1 {
4 @include apply-svg-color(#7d7d7d); 4 my-feed {
5 margin-left: 5px;
6 display: inline-block;
7
8 ::ng-deep {
9 my-global-icon {
10 width: 15px !important;
11 top: 0 !important;
12 }
13 }
14 }
15}
5 16
6 width: 12px; 17my-global-icon {
7 height: 12px; 18 width: 24px;
8 position: relative; 19 height: 24px;
9 top: -1px;
10} 20}
11 21
12.input-group { 22.input-group {
@@ -25,3 +35,32 @@ my-global-icon {
25 flex-grow: 1; 35 flex-grow: 1;
26 } 36 }
27} 37}
38
39.video {
40 display: flex;
41 flex-direction: column;
42
43 em {
44 font-size: 11px;
45 }
46
47 a {
48 @include ellipsis
49 }
50}
51
52.comment-html {
53 ::ng-deep {
54 > div {
55 max-height: 22px;
56 }
57
58 div, p {
59 @include ellipsis;
60 }
61
62 p {
63 margin: 0;
64 }
65 }
66}
diff --git a/client/src/app/+admin/moderation/video-comment-list/video-comment-list.component.ts b/client/src/app/+admin/moderation/video-comment-list/video-comment-list.component.ts
index fdd5ec76e..d26047125 100644
--- a/client/src/app/+admin/moderation/video-comment-list/video-comment-list.component.ts
+++ b/client/src/app/+admin/moderation/video-comment-list/video-comment-list.component.ts
@@ -1,16 +1,17 @@
1import { SortMeta } from 'primeng/api' 1import { SortMeta } from 'primeng/api'
2import { filter } from 'rxjs/operators' 2import { filter } from 'rxjs/operators'
3import { AfterViewInit, Component, OnInit } from '@angular/core' 3import { AfterViewInit, Component, OnInit } from '@angular/core'
4import { DomSanitizer } from '@angular/platform-browser'
5import { ActivatedRoute, Params, Router } from '@angular/router' 4import { ActivatedRoute, Params, Router } from '@angular/router'
6import { ConfirmService, MarkdownService, Notifier, RestPagination, RestTable, ServerService } from '@app/core' 5import { AuthService, ConfirmService, MarkdownService, Notifier, RestPagination, RestTable } from '@app/core'
7import { DropdownAction, VideoService } from '@app/shared/shared-main' 6import { DropdownAction } from '@app/shared/shared-main'
7import { BulkService } from '@app/shared/shared-moderation'
8import { VideoCommentAdmin, VideoCommentService } from '@app/shared/shared-video-comment' 8import { VideoCommentAdmin, VideoCommentService } from '@app/shared/shared-video-comment'
9import { FeedFormat, UserRight } from '@shared/models'
9 10
10@Component({ 11@Component({
11 selector: 'my-video-comment-list', 12 selector: 'my-video-comment-list',
12 templateUrl: './video-comment-list.component.html', 13 templateUrl: './video-comment-list.component.html',
13 styleUrls: [ './video-comment-list.component.scss' ] 14 styleUrls: [ '../../../shared/shared-moderation/moderation.scss', './video-comment-list.component.scss' ]
14}) 15})
15export class VideoCommentListComponent extends RestTable implements OnInit, AfterViewInit { 16export class VideoCommentListComponent extends RestTable implements OnInit, AfterViewInit {
16 comments: VideoCommentAdmin[] 17 comments: VideoCommentAdmin[]
@@ -20,26 +21,54 @@ export class VideoCommentListComponent extends RestTable implements OnInit, Afte
20 21
21 videoCommentActions: DropdownAction<VideoCommentAdmin>[][] = [] 22 videoCommentActions: DropdownAction<VideoCommentAdmin>[][] = []
22 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 get authUser () {
43 return this.auth.getUser()
44 }
45
23 constructor ( 46 constructor (
47 private auth: AuthService,
24 private notifier: Notifier, 48 private notifier: Notifier,
25 private serverService: ServerService,
26 private confirmService: ConfirmService, 49 private confirmService: ConfirmService,
27 private videoCommentService: VideoCommentService, 50 private videoCommentService: VideoCommentService,
28 private markdownRenderer: MarkdownService, 51 private markdownRenderer: MarkdownService,
29 private sanitizer: DomSanitizer,
30 private videoService: VideoService,
31 private route: ActivatedRoute, 52 private route: ActivatedRoute,
32 private router: Router 53 private router: Router,
54 private bulkService: BulkService
33 ) { 55 ) {
34 super() 56 super()
35 57
36 this.videoCommentActions = [ 58 this.videoCommentActions = [
37 [ 59 [
60 {
61 label: $localize`Delete this comment`,
62 handler: comment => this.deleteComment(comment),
63 isDisplayed: () => this.authUser.hasRight(UserRight.REMOVE_ANY_VIDEO_COMMENT)
64 },
38 65
39 // remove this comment, 66 {
40 67 label: $localize`Delete all comments of this account`,
41 // remove all comments of this account 68 description: $localize`Comments are deleted after a few minutes`,
42 69 handler: comment => this.deleteUserComments(comment),
70 isDisplayed: () => this.authUser.hasRight(UserRight.REMOVE_ANY_VIDEO_COMMENT)
71 }
43 ] 72 ]
44 ] 73 ]
45 } 74 }
@@ -60,7 +89,7 @@ export class VideoCommentListComponent extends RestTable implements OnInit, Afte
60 if (this.search) this.setTableFilter(this.search) 89 if (this.search) this.setTableFilter(this.search)
61 } 90 }
62 91
63 onSearch (event: Event) { 92 onInputSearch (event: Event) {
64 this.onSearch(event) 93 this.onSearch(event)
65 this.setQueryParams((event.target as HTMLInputElement).value) 94 this.setQueryParams((event.target as HTMLInputElement).value)
66 } 95 }
@@ -84,7 +113,7 @@ export class VideoCommentListComponent extends RestTable implements OnInit, Afte
84 } 113 }
85 114
86 toHtml (text: string) { 115 toHtml (text: string) {
87 return this.markdownRenderer.textMarkdownToHTML(text) 116 return this.markdownRenderer.textMarkdownToHTML(text, true, true)
88 } 117 }
89 118
90 protected loadData () { 119 protected loadData () {
@@ -108,4 +137,33 @@ export class VideoCommentListComponent extends RestTable implements OnInit, Afte
108 err => this.notifier.error(err.message) 137 err => this.notifier.error(err.message)
109 ) 138 )
110 } 139 }
140
141 private deleteComment (comment: VideoCommentAdmin) {
142 this.videoCommentService.deleteVideoComment(comment.video.id, comment.id)
143 .subscribe(
144 () => this.loadData(),
145
146 err => this.notifier.error(err.message)
147 )
148 }
149
150 private async deleteUserComments (comment: VideoCommentAdmin) {
151 const message = $localize`Do you really want to delete all comments of ${comment.by}?`
152 const res = await this.confirmService.confirm(message, $localize`Delete`)
153 if (res === false) return
154
155 const options = {
156 accountName: comment.by,
157 scope: 'instance' as 'instance'
158 }
159
160 this.bulkService.removeCommentsOf(options)
161 .subscribe(
162 () => {
163 this.notifier.success($localize`Comments of ${options.accountName} will be deleted in a few minutes`)
164 },
165
166 err => this.notifier.error(err.message)
167 )
168 }
111} 169}
diff --git a/client/src/app/shared/shared-main/feeds/feed.component.html b/client/src/app/shared/shared-main/feeds/feed.component.html
index 13883fd9b..a00011785 100644
--- a/client/src/app/shared/shared-main/feeds/feed.component.html
+++ b/client/src/app/shared/shared-main/feeds/feed.component.html
@@ -1,4 +1,4 @@
1<div class="video-feed"> 1<div class="feed">
2 <my-global-icon 2 <my-global-icon
3 *ngIf="syndicationItems.length !== 0" [ngbPopover]="feedsList" [autoClose]="true" placement="bottom left auto" 3 *ngIf="syndicationItems.length !== 0" [ngbPopover]="feedsList" [autoClose]="true" placement="bottom left auto"
4 class="icon-syndication" role="button" iconName="syndication" 4 class="icon-syndication" role="button" iconName="syndication"
diff --git a/client/src/app/shared/shared-main/feeds/feed.component.scss b/client/src/app/shared/shared-main/feeds/feed.component.scss
index 34dd0e937..333d59440 100644
--- a/client/src/app/shared/shared-main/feeds/feed.component.scss
+++ b/client/src/app/shared/shared-main/feeds/feed.component.scss
@@ -1,7 +1,7 @@
1@import '_variables'; 1@import '_variables';
2@import '_mixins'; 2@import '_mixins';
3 3
4.video-feed { 4.feed {
5 width: min-content; 5 width: min-content;
6 6
7 a { 7 a {
diff --git a/client/src/app/shared/shared-video-comment/video-comment.model.ts b/client/src/app/shared/shared-video-comment/video-comment.model.ts
index 1589091e5..eeee397af 100644
--- a/client/src/app/shared/shared-video-comment/video-comment.model.ts
+++ b/client/src/app/shared/shared-video-comment/video-comment.model.ts
@@ -59,12 +59,14 @@ export class VideoCommentAdmin implements VideoCommentAdminServerModel {
59 createdAt: Date | string 59 createdAt: Date | string
60 updatedAt: Date | string 60 updatedAt: Date | string
61 61
62 account: AccountInterface 62 account: AccountInterface & { localUrl?: string }
63 localUrl: string
63 64
64 video: { 65 video: {
65 id: number 66 id: number
66 uuid: string 67 uuid: string
67 name: string 68 name: string
69 localUrl: string
68 } 70 }
69 71
70 by: string 72 by: string
@@ -85,14 +87,19 @@ export class VideoCommentAdmin implements VideoCommentAdminServerModel {
85 this.video = { 87 this.video = {
86 id: hash.video.id, 88 id: hash.video.id,
87 uuid: hash.video.uuid, 89 uuid: hash.video.uuid,
88 name: hash.video.name 90 name: hash.video.name,
91 localUrl: '/videos/watch/' + hash.video.uuid
89 } 92 }
90 93
94 this.localUrl = this.video.localUrl + ';threadId=' + this.threadId
95
91 this.account = hash.account 96 this.account = hash.account
92 97
93 if (this.account) { 98 if (this.account) {
94 this.by = Actor.CREATE_BY_STRING(this.account.name, this.account.host) 99 this.by = Actor.CREATE_BY_STRING(this.account.name, this.account.host)
95 this.accountAvatarUrl = Actor.GET_ACTOR_AVATAR_URL(this.account) 100 this.accountAvatarUrl = Actor.GET_ACTOR_AVATAR_URL(this.account)
101
102 this.account.localUrl = '/accounts/' + this.by
96 } 103 }
97 } 104 }
98} 105}
diff --git a/client/src/app/shared/shared-video-comment/video-comment.service.ts b/client/src/app/shared/shared-video-comment/video-comment.service.ts
index e318e069d..1ab996a76 100644
--- a/client/src/app/shared/shared-video-comment/video-comment.service.ts
+++ b/client/src/app/shared/shared-video-comment/video-comment.service.ts
@@ -19,8 +19,9 @@ import { SortMeta } from 'primeng/api'
19 19
20@Injectable() 20@Injectable()
21export class VideoCommentService { 21export class VideoCommentService {
22 static BASE_FEEDS_URL = environment.apiUrl + '/feeds/video-comments.'
23
22 private static BASE_VIDEO_URL = environment.apiUrl + '/api/v1/videos/' 24 private static BASE_VIDEO_URL = environment.apiUrl + '/api/v1/videos/'
23 private static BASE_FEEDS_URL = environment.apiUrl + '/feeds/video-comments.'
24 25
25 constructor ( 26 constructor (
26 private authHttp: HttpClient, 27 private authHttp: HttpClient,
@@ -56,7 +57,7 @@ export class VideoCommentService {
56 search?: string 57 search?: string
57 }): Observable<ResultList<VideoCommentAdmin>> { 58 }): Observable<ResultList<VideoCommentAdmin>> {
58 const { pagination, sort, search } = options 59 const { pagination, sort, search } = options
59 const url = VideoCommentService.BASE_VIDEO_URL + '/comments' 60 const url = VideoCommentService.BASE_VIDEO_URL + 'comments'
60 61
61 let params = new HttpParams() 62 let params = new HttpParams()
62 params = this.restService.addRestGetParams(params, pagination, sort) 63 params = this.restService.addRestGetParams(params, pagination, sort)
@@ -172,7 +173,7 @@ export class VideoCommentService {
172 173
173 private buildParamsFromSearch (search: string, params: HttpParams) { 174 private buildParamsFromSearch (search: string, params: HttpParams) {
174 const filters = this.restService.parseQueryStringFilter(search, { 175 const filters = this.restService.parseQueryStringFilter(search, {
175 state: { 176 isLocal: {
176 prefix: 'local:', 177 prefix: 'local:',
177 isBoolean: true, 178 isBoolean: true,
178 handler: v => { 179 handler: v => {