aboutsummaryrefslogtreecommitdiffhomepage
path: root/client/src
diff options
context:
space:
mode:
authorChocobozzz <me@florianbigard.com>2020-11-16 13:49:09 +0100
committerChocobozzz <me@florianbigard.com>2020-11-16 13:57:14 +0100
commit19149d45b8f68569535f7188ef25e09e3d62c8b4 (patch)
tree92da0e0db2b0e277b2110a630f502433c86c9119 /client/src
parent8872828d59a5152e27734711ae30ebe86e84f570 (diff)
parentf1273314593a4a7dc7ec9594ce0c6c3ae8f62b34 (diff)
downloadPeerTube-19149d45b8f68569535f7188ef25e09e3d62c8b4.tar.gz
PeerTube-19149d45b8f68569535f7188ef25e09e3d62c8b4.tar.zst
PeerTube-19149d45b8f68569535f7188ef25e09e3d62c8b4.zip
Merge branch 'feature/admin-comments' into develop
Diffstat (limited to 'client/src')
-rw-r--r--client/src/app/+admin/admin.component.ts11
-rw-r--r--client/src/app/+admin/admin.module.ts4
-rw-r--r--client/src/app/+admin/moderation/moderation.routes.ts24
-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/index.ts1
-rw-r--r--client/src/app/+admin/moderation/video-comment-list/video-comment-list.component.html118
-rw-r--r--client/src/app/+admin/moderation/video-comment-list/video-comment-list.component.scss66
-rw-r--r--client/src/app/+admin/moderation/video-comment-list/video-comment-list.component.ts169
-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.ts59
-rw-r--r--client/src/app/shared/shared-video-comment/video-comment.service.ts48
12 files changed, 499 insertions, 13 deletions
diff --git a/client/src/app/+admin/admin.component.ts b/client/src/app/+admin/admin.component.ts
index b661a5517..dd92ed2ca 100644
--- a/client/src/app/+admin/admin.component.ts
+++ b/client/src/app/+admin/admin.component.ts
@@ -62,6 +62,13 @@ export class AdminComponent implements OnInit {
62 iconName: 'cross' 62 iconName: 'cross'
63 }) 63 })
64 } 64 }
65 if (this.hasVideoCommentsRight()) {
66 moderationItems.children.push({
67 label: $localize`Video comments`,
68 routerLink: '/admin/moderation/video-comments/list',
69 iconName: 'message-circle'
70 })
71 }
65 if (this.hasAccountsBlocklistRight()) { 72 if (this.hasAccountsBlocklistRight()) {
66 moderationItems.children.push({ 73 moderationItems.children.push({
67 label: $localize`Muted accounts`, 74 label: $localize`Muted accounts`,
@@ -140,4 +147,8 @@ export class AdminComponent implements OnInit {
140 hasDebugRight () { 147 hasDebugRight () {
141 return this.auth.getUser().hasRight(UserRight.MANAGE_DEBUG) 148 return this.auth.getUser().hasRight(UserRight.MANAGE_DEBUG)
142 } 149 }
150
151 hasVideoCommentsRight () {
152 return this.auth.getUser().hasRight(UserRight.SEE_ALL_COMMENTS)
153 }
143} 154}
diff --git a/client/src/app/+admin/admin.module.ts b/client/src/app/+admin/admin.module.ts
index da517a55b..5c0864f48 100644
--- a/client/src/app/+admin/admin.module.ts
+++ b/client/src/app/+admin/admin.module.ts
@@ -7,6 +7,7 @@ import { SharedFormModule } from '@app/shared/shared-forms'
7import { SharedGlobalIconModule } from '@app/shared/shared-icons' 7import { SharedGlobalIconModule } from '@app/shared/shared-icons'
8import { SharedMainModule } from '@app/shared/shared-main' 8import { SharedMainModule } from '@app/shared/shared-main'
9import { SharedModerationModule } from '@app/shared/shared-moderation' 9import { SharedModerationModule } from '@app/shared/shared-moderation'
10import { SharedVideoCommentModule } from '@app/shared/shared-video-comment'
10import { AdminRoutingModule } from './admin-routing.module' 11import { AdminRoutingModule } from './admin-routing.module'
11import { AdminComponent } from './admin.component' 12import { AdminComponent } from './admin.component'
12import { ConfigComponent, EditCustomConfigComponent } from './config' 13import { ConfigComponent, EditCustomConfigComponent } from './config'
@@ -18,6 +19,7 @@ import { VideoRedundancyInformationComponent } from './follows/video-redundancie
18import { AbuseListComponent, VideoBlockListComponent } from './moderation' 19import { AbuseListComponent, VideoBlockListComponent } from './moderation'
19import { InstanceAccountBlocklistComponent, InstanceServerBlocklistComponent } from './moderation/instance-blocklist' 20import { InstanceAccountBlocklistComponent, InstanceServerBlocklistComponent } from './moderation/instance-blocklist'
20import { ModerationComponent } from './moderation/moderation.component' 21import { ModerationComponent } from './moderation/moderation.component'
22import { VideoCommentListComponent } from './moderation/video-comment-list'
21import { PluginListInstalledComponent } from './plugins/plugin-list-installed/plugin-list-installed.component' 23import { PluginListInstalledComponent } from './plugins/plugin-list-installed/plugin-list-installed.component'
22import { PluginSearchComponent } from './plugins/plugin-search/plugin-search.component' 24import { PluginSearchComponent } from './plugins/plugin-search/plugin-search.component'
23import { PluginShowInstalledComponent } from './plugins/plugin-show-installed/plugin-show-installed.component' 25import { PluginShowInstalledComponent } from './plugins/plugin-show-installed/plugin-show-installed.component'
@@ -37,6 +39,7 @@ import { UserCreateComponent, UserListComponent, UserPasswordComponent, UsersCom
37 SharedModerationModule, 39 SharedModerationModule,
38 SharedGlobalIconModule, 40 SharedGlobalIconModule,
39 SharedAbuseListModule, 41 SharedAbuseListModule,
42 SharedVideoCommentModule,
40 43
41 TableModule, 44 TableModule,
42 SelectButtonModule, 45 SelectButtonModule,
@@ -62,6 +65,7 @@ import { UserCreateComponent, UserListComponent, UserPasswordComponent, UsersCom
62 ModerationComponent, 65 ModerationComponent,
63 VideoBlockListComponent, 66 VideoBlockListComponent,
64 AbuseListComponent, 67 AbuseListComponent,
68 VideoCommentListComponent,
65 69
66 InstanceServerBlocklistComponent, 70 InstanceServerBlocklistComponent,
67 InstanceAccountBlocklistComponent, 71 InstanceAccountBlocklistComponent,
diff --git a/client/src/app/+admin/moderation/moderation.routes.ts b/client/src/app/+admin/moderation/moderation.routes.ts
index b60dd5334..2e28f0911 100644
--- a/client/src/app/+admin/moderation/moderation.routes.ts
+++ b/client/src/app/+admin/moderation/moderation.routes.ts
@@ -1,8 +1,9 @@
1import { Routes } from '@angular/router' 1import { Routes } from '@angular/router'
2import { AbuseListComponent } from '@app/+admin/moderation/abuse-list'
2import { InstanceAccountBlocklistComponent, InstanceServerBlocklistComponent } from '@app/+admin/moderation/instance-blocklist' 3import { InstanceAccountBlocklistComponent, InstanceServerBlocklistComponent } from '@app/+admin/moderation/instance-blocklist'
3import { ModerationComponent } from '@app/+admin/moderation/moderation.component' 4import { ModerationComponent } from '@app/+admin/moderation/moderation.component'
4import { AbuseListComponent } from '@app/+admin/moderation/abuse-list'
5import { VideoBlockListComponent } from '@app/+admin/moderation/video-block-list' 5import { VideoBlockListComponent } from '@app/+admin/moderation/video-block-list'
6import { VideoCommentListComponent } from './video-comment-list'
6import { UserRightGuard } from '@app/core' 7import { UserRightGuard } from '@app/core'
7import { UserRight } from '@shared/models' 8import { UserRight } from '@shared/models'
8 9
@@ -37,6 +38,7 @@ export const ModerationRoutes: Routes = [
37 } 38 }
38 } 39 }
39 }, 40 },
41
40 { 42 {
41 path: 'video-blacklist', 43 path: 'video-blacklist',
42 redirectTo: 'video-blocks/list', 44 redirectTo: 'video-blocks/list',
@@ -64,10 +66,28 @@ export const ModerationRoutes: Routes = [
64 data: { 66 data: {
65 userRight: UserRight.MANAGE_VIDEO_BLACKLIST, 67 userRight: UserRight.MANAGE_VIDEO_BLACKLIST,
66 meta: { 68 meta: {
67 title: $localize`Videos blocked` 69 title: $localize`Blocked videos`
68 } 70 }
69 } 71 }
70 }, 72 },
73
74 {
75 path: 'video-comments',
76 redirectTo: 'video-comments/list',
77 pathMatch: 'full'
78 },
79 {
80 path: 'video-comments/list',
81 component: VideoCommentListComponent,
82 canActivate: [ UserRightGuard ],
83 data: {
84 userRight: UserRight.SEE_ALL_COMMENTS,
85 meta: {
86 title: $localize`Video comments`
87 }
88 }
89 },
90
71 { 91 {
72 path: 'blocklist/accounts', 92 path: 'blocklist/accounts',
73 component: InstanceAccountBlocklistComponent, 93 component: InstanceAccountBlocklistComponent,
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/index.ts b/client/src/app/+admin/moderation/video-comment-list/index.ts
new file mode 100644
index 000000000..eb08b4177
--- /dev/null
+++ b/client/src/app/+admin/moderation/video-comment-list/index.ts
@@ -0,0 +1 @@
export * from './video-comment-list.component'
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
new file mode 100644
index 000000000..45c5fe28f
--- /dev/null
+++ b/client/src/app/+admin/moderation/video-comment-list/video-comment-list.component.html
@@ -0,0 +1,118 @@
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>This view also shows comments from muted accounts.</em>
9
10<p-table
11 [value]="comments" [lazy]="true" [paginator]="totalRecords > 0" [totalRecords]="totalRecords" [rows]="rowsPerPage" [rowsPerPageOptions]="rowsPerPageOptions"
12 [sortField]="sort.field" [sortOrder]="sort.order" (onLazyLoad)="loadLazy($event)" dataKey="id"
13 [showCurrentPageReport]="true" i18n-currentPageReportTemplate
14 currentPageReportTemplate="Showing {{'{first}'}} to {{'{last}'}} of {{'{totalRecords}'}} comments"
15 (onPage)="onPage($event)" [expandedRowKeys]="expandedRows"
16>
17 <ng-template pTemplate="caption">
18 <div class="caption">
19 <div class="ml-auto">
20 <div class="input-group has-feedback has-clear">
21 <div class="input-group-prepend c-hand" ngbDropdown placement="bottom-left auto" container="body">
22 <div class="input-group-text" ngbDropdownToggle>
23 <span class="caret" aria-haspopup="menu" role="button"></span>
24 </div>
25
26 <div role="menu" ngbDropdownMenu>
27 <h6 class="dropdown-header" i18n>Advanced comments filters</h6>
28 <a [routerLink]="[ '/admin/moderation/video-comments/list' ]" [queryParams]="{ 'search': 'local:true' }" class="dropdown-item" i18n>Local comments</a>
29 <a [routerLink]="[ '/admin/moderation/video-comments/list' ]" [queryParams]="{ 'search': 'local:false' }" class="dropdown-item" i18n>Remote comments</a>
30 </div>
31 </div>
32 <input
33 type="text" name="table-filter" id="table-filter" i18n-placeholder placeholder="Filter..."
34 (keyup)="onInputSearch($event)"
35 >
36 <a class="glyphicon glyphicon-remove-sign form-control-feedback form-control-clear" (click)="resetTableFilter()"></a>
37 <span class="sr-only" i18n>Clear filters</span>
38 </div>
39 </div>
40 </div>
41 </ng-template>
42
43 <ng-template pTemplate="header">
44 <tr>
45 <th style="width: 40px"></th>
46 <th style="width: 300px" i18n>Account</th>
47 <th style="width: 300px" i18n>Video</th>
48 <th i18n>Comment</th>
49 <th style="width: 150px;" i18n pSortableColumn="createdAt">Date <p-sortIcon field="createdAt"></p-sortIcon></th>
50 <th style="width: 150px;"></th>
51 </tr>
52 </ng-template>
53
54 <ng-template pTemplate="body" let-videoComment let-expanded="expanded">
55 <tr>
56 <td class="expand-cell c-hand" [pRowToggler]="videoComment" i18n-ngbTooltip ngbTooltip="More information" placement="top-left" container="body">
57 <span class="expander">
58 <i [ngClass]="expanded ? 'glyphicon glyphicon-menu-down' : 'glyphicon glyphicon-menu-right'"></i>
59 </span>
60 </td>
61
62 <td>
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>
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">
85 <div [innerHTML]="videoComment.textHtml"></div>
86 </td>
87
88 <td>{{ videoComment.createdAt | date: 'short' }}</td>
89
90 <td class="action-cell">
91 <my-action-dropdown
92 [ngClass]="{ 'show': expanded }" placement="bottom-right" container="body"
93 i18n-label label="Actions" [actions]="videoCommentActions" [entry]="videoComment"
94 ></my-action-dropdown>
95 </td>
96 </tr>
97 </ng-template>
98
99 <ng-template pTemplate="rowexpansion" let-videoComment>
100 <tr>
101 <td class="expand-cell" colspan="5">
102 <div [innerHTML]="videoComment.textHtml"></div>
103 </td>
104 </tr>
105 </ng-template>
106
107 <ng-template pTemplate="emptymessage">
108 <tr>
109 <td colspan="5">
110 <div class="no-results">
111 <ng-container *ngIf="search" i18n>No comments found matching current filters.</ng-container>
112 <ng-container *ngIf="!search" i18n>No comments found.</ng-container>
113 </div>
114 </td>
115 </tr>
116 </ng-template>
117</p-table>
118
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
new file mode 100644
index 000000000..439835899
--- /dev/null
+++ b/client/src/app/+admin/moderation/video-comment-list/video-comment-list.component.scss
@@ -0,0 +1,66 @@
1@import 'mixins';
2
3h1 {
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}
16
17my-global-icon {
18 width: 24px;
19 height: 24px;
20}
21
22.input-group {
23 @include peertube-input-group(300px);
24
25 .dropdown-toggle::after {
26 margin-left: 0;
27 }
28}
29
30.caption {
31 justify-content: flex-end;
32
33 input {
34 @include peertube-input-text(250px);
35 flex-grow: 1;
36 }
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
new file mode 100644
index 000000000..d26047125
--- /dev/null
+++ b/client/src/app/+admin/moderation/video-comment-list/video-comment-list.component.ts
@@ -0,0 +1,169 @@
1import { SortMeta } from 'primeng/api'
2import { filter } from 'rxjs/operators'
3import { AfterViewInit, Component, OnInit } from '@angular/core'
4import { ActivatedRoute, Params, Router } from '@angular/router'
5import { AuthService, ConfirmService, MarkdownService, Notifier, RestPagination, RestTable } from '@app/core'
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, AfterViewInit {
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 get authUser () {
43 return this.auth.getUser()
44 }
45
46 constructor (
47 private auth: AuthService,
48 private notifier: Notifier,
49 private confirmService: ConfirmService,
50 private videoCommentService: VideoCommentService,
51 private markdownRenderer: MarkdownService,
52 private route: ActivatedRoute,
53 private router: Router,
54 private bulkService: BulkService
55 ) {
56 super()
57
58 this.videoCommentActions = [
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 },
65
66 {
67 label: $localize`Delete all comments of this account`,
68 description: $localize`Comments are deleted after a few minutes`,
69 handler: comment => this.deleteUserComments(comment),
70 isDisplayed: () => this.authUser.hasRight(UserRight.REMOVE_ANY_VIDEO_COMMENT)
71 }
72 ]
73 ]
74 }
75
76 ngOnInit () {
77 this.initialize()
78
79 this.route.queryParams
80 .pipe(filter(params => params.search !== undefined && params.search !== null))
81 .subscribe(params => {
82 this.search = params.search
83 this.setTableFilter(params.search)
84 this.loadData()
85 })
86 }
87
88 ngAfterViewInit () {
89 if (this.search) this.setTableFilter(this.search)
90 }
91
92 onInputSearch (event: Event) {
93 this.onSearch(event)
94 this.setQueryParams((event.target as HTMLInputElement).value)
95 }
96
97 setQueryParams (search: string) {
98 const queryParams: Params = {}
99
100 if (search) Object.assign(queryParams, { search })
101 this.router.navigate([ '/admin/moderation/video-comments/list' ], { queryParams })
102 }
103
104 resetTableFilter () {
105 this.setTableFilter('')
106 this.setQueryParams('')
107 this.resetSearch()
108 }
109 /* END Table filter functions */
110
111 getIdentifier () {
112 return 'VideoCommentListComponent'
113 }
114
115 toHtml (text: string) {
116 return this.markdownRenderer.textMarkdownToHTML(text, true, true)
117 }
118
119 protected loadData () {
120 this.videoCommentService.getAdminVideoComments({
121 pagination: this.pagination,
122 sort: this.sort,
123 search: this.search
124 }).subscribe(
125 async resultList => {
126 this.totalRecords = resultList.total
127
128 this.comments = []
129
130 for (const c of resultList.data) {
131 this.comments.push(
132 new VideoCommentAdmin(c, await this.toHtml(c.text))
133 )
134 }
135 },
136
137 err => this.notifier.error(err.message)
138 )
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 }
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 e85443196..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
@@ -1,6 +1,6 @@
1import { getAbsoluteAPIUrl } from '@app/helpers' 1import { getAbsoluteAPIUrl } from '@app/helpers'
2import { Actor } from '@app/shared/shared-main' 2import { Actor } from '@app/shared/shared-main'
3import { Account as AccountInterface, VideoComment as VideoCommentServerModel } from '@shared/models' 3import { Account as AccountInterface, VideoComment as VideoCommentServerModel, VideoCommentAdmin as VideoCommentAdminServerModel } from '@shared/models'
4 4
5export class VideoComment implements VideoCommentServerModel { 5export class VideoComment implements VideoCommentServerModel {
6 id: number 6 id: number
@@ -46,3 +46,60 @@ export class VideoComment implements VideoCommentServerModel {
46 } 46 }
47 } 47 }
48} 48}
49
50export class VideoCommentAdmin implements VideoCommentAdminServerModel {
51 id: number
52 url: string
53 text: string
54 textHtml: string
55
56 threadId: number
57 inReplyToCommentId: number
58
59 createdAt: Date | string
60 updatedAt: Date | string
61
62 account: AccountInterface & { localUrl?: string }
63 localUrl: string
64
65 video: {
66 id: number
67 uuid: string
68 name: string
69 localUrl: string
70 }
71
72 by: string
73 accountAvatarUrl: string
74
75 constructor (hash: VideoCommentAdminServerModel, textHtml: string) {
76 this.id = hash.id
77 this.url = hash.url
78 this.text = hash.text
79 this.textHtml = textHtml
80
81 this.threadId = hash.threadId
82 this.inReplyToCommentId = hash.inReplyToCommentId
83
84 this.createdAt = new Date(hash.createdAt.toString())
85 this.updatedAt = new Date(hash.updatedAt.toString())
86
87 this.video = {
88 id: hash.video.id,
89 uuid: hash.video.uuid,
90 name: hash.video.name,
91 localUrl: '/videos/watch/' + hash.video.uuid
92 }
93
94 this.localUrl = this.video.localUrl + ';threadId=' + this.threadId
95
96 this.account = hash.account
97
98 if (this.account) {
99 this.by = Actor.CREATE_BY_STRING(this.account.name, this.account.host)
100 this.accountAvatarUrl = Actor.GET_ACTOR_AVATAR_URL(this.account)
101
102 this.account.localUrl = '/accounts/' + this.by
103 }
104 }
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 81c65aa38..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
@@ -2,23 +2,26 @@ import { Observable } from 'rxjs'
2import { catchError, map } from 'rxjs/operators' 2import { catchError, map } from 'rxjs/operators'
3import { HttpClient, HttpParams } from '@angular/common/http' 3import { HttpClient, HttpParams } from '@angular/common/http'
4import { Injectable } from '@angular/core' 4import { Injectable } from '@angular/core'
5import { ComponentPaginationLight, RestExtractor, RestService } from '@app/core' 5import { ComponentPaginationLight, RestExtractor, RestPagination, RestService } from '@app/core'
6import { objectLineFeedToHtml } from '@app/helpers' 6import { objectLineFeedToHtml } from '@app/helpers'
7import { 7import {
8 FeedFormat, 8 FeedFormat,
9 ResultList, 9 ResultList,
10 VideoComment as VideoCommentServerModel, 10 VideoComment as VideoCommentServerModel,
11 VideoCommentAdmin,
11 VideoCommentCreate, 12 VideoCommentCreate,
12 VideoCommentThreadTree as VideoCommentThreadTreeServerModel 13 VideoCommentThreadTree as VideoCommentThreadTreeServerModel
13} from '@shared/models' 14} from '@shared/models'
14import { environment } from '../../../environments/environment' 15import { environment } from '../../../environments/environment'
15import { VideoCommentThreadTree } from './video-comment-thread-tree.model' 16import { VideoCommentThreadTree } from './video-comment-thread-tree.model'
16import { VideoComment } from './video-comment.model' 17import { VideoComment } from './video-comment.model'
18import { SortMeta } from 'primeng/api'
17 19
18@Injectable() 20@Injectable()
19export class VideoCommentService { 21export class VideoCommentService {
22 static BASE_FEEDS_URL = environment.apiUrl + '/feeds/video-comments.'
23
20 private static BASE_VIDEO_URL = environment.apiUrl + '/api/v1/videos/' 24 private static BASE_VIDEO_URL = environment.apiUrl + '/api/v1/videos/'
21 private static BASE_FEEDS_URL = environment.apiUrl + '/feeds/video-comments.'
22 25
23 constructor ( 26 constructor (
24 private authHttp: HttpClient, 27 private authHttp: HttpClient,
@@ -48,6 +51,27 @@ export class VideoCommentService {
48 ) 51 )
49 } 52 }
50 53
54 getAdminVideoComments (options: {
55 pagination: RestPagination,
56 sort: SortMeta,
57 search?: string
58 }): Observable<ResultList<VideoCommentAdmin>> {
59 const { pagination, sort, search } = options
60 const url = VideoCommentService.BASE_VIDEO_URL + 'comments'
61
62 let params = new HttpParams()
63 params = this.restService.addRestGetParams(params, pagination, sort)
64
65 if (search) {
66 params = this.buildParamsFromSearch(search, params)
67 }
68
69 return this.authHttp.get<ResultList<VideoCommentAdmin>>(url, { params })
70 .pipe(
71 catchError(res => this.restExtractor.handleError(res))
72 )
73 }
74
51 getVideoCommentThreads (parameters: { 75 getVideoCommentThreads (parameters: {
52 videoId: number | string, 76 videoId: number | string,
53 componentPagination: ComponentPaginationLight, 77 componentPagination: ComponentPaginationLight,
@@ -146,4 +170,24 @@ export class VideoCommentService {
146 170
147 return tree as VideoCommentThreadTree 171 return tree as VideoCommentThreadTree
148 } 172 }
173
174 private buildParamsFromSearch (search: string, params: HttpParams) {
175 const filters = this.restService.parseQueryStringFilter(search, {
176 isLocal: {
177 prefix: 'local:',
178 isBoolean: true,
179 handler: v => {
180 if (v === 'true') return v
181 if (v === 'false') return v
182
183 return undefined
184 }
185 },
186
187 searchAccount: { prefix: 'account:' },
188 searchVideo: { prefix: 'video:' }
189 })
190
191 return this.restService.addObjectParams(params, filters)
192 }
149} 193}