diff options
Diffstat (limited to 'client/src/app/+videos')
8 files changed, 38 insertions, 216 deletions
diff --git a/client/src/app/+videos/+video-watch/comment/video-comment-add.component.ts b/client/src/app/+videos/+video-watch/comment/video-comment-add.component.ts index 79505c779..d79efbb49 100644 --- a/client/src/app/+videos/+video-watch/comment/video-comment-add.component.ts +++ b/client/src/app/+videos/+video-watch/comment/video-comment-add.component.ts | |||
@@ -4,10 +4,9 @@ import { Router } from '@angular/router' | |||
4 | import { Notifier, User } from '@app/core' | 4 | import { Notifier, User } from '@app/core' |
5 | import { FormReactive, FormValidatorService, VideoCommentValidatorsService } from '@app/shared/shared-forms' | 5 | import { FormReactive, FormValidatorService, VideoCommentValidatorsService } from '@app/shared/shared-forms' |
6 | import { Video } from '@app/shared/shared-main' | 6 | import { Video } from '@app/shared/shared-main' |
7 | import { VideoComment, VideoCommentService } from '@app/shared/shared-video-comment' | ||
7 | import { NgbModal } from '@ng-bootstrap/ng-bootstrap' | 8 | import { NgbModal } from '@ng-bootstrap/ng-bootstrap' |
8 | import { VideoCommentCreate } from '@shared/models' | 9 | import { VideoCommentCreate } from '@shared/models' |
9 | import { VideoComment } from './video-comment.model' | ||
10 | import { VideoCommentService } from './video-comment.service' | ||
11 | 10 | ||
12 | @Component({ | 11 | @Component({ |
13 | selector: 'my-video-comment-add', | 12 | selector: 'my-video-comment-add', |
diff --git a/client/src/app/+videos/+video-watch/comment/video-comment-thread-tree.model.ts b/client/src/app/+videos/+video-watch/comment/video-comment-thread-tree.model.ts deleted file mode 100644 index 7c2aaeadd..000000000 --- a/client/src/app/+videos/+video-watch/comment/video-comment-thread-tree.model.ts +++ /dev/null | |||
@@ -1,7 +0,0 @@ | |||
1 | import { VideoCommentThreadTree as VideoCommentThreadTreeServerModel } from '@shared/models' | ||
2 | import { VideoComment } from './video-comment.model' | ||
3 | |||
4 | export class VideoCommentThreadTree implements VideoCommentThreadTreeServerModel { | ||
5 | comment: VideoComment | ||
6 | children: VideoCommentThreadTree[] | ||
7 | } | ||
diff --git a/client/src/app/+videos/+video-watch/comment/video-comment.component.html b/client/src/app/+videos/+video-watch/comment/video-comment.component.html index 002de57e4..f02ea549a 100644 --- a/client/src/app/+videos/+video-watch/comment/video-comment.component.html +++ b/client/src/app/+videos/+video-watch/comment/video-comment.component.html | |||
@@ -45,6 +45,7 @@ | |||
45 | <div *ngIf="isRemovableByUser()" (click)="onWantToDelete()" class="comment-action-delete" i18n>Delete</div> | 45 | <div *ngIf="isRemovableByUser()" (click)="onWantToDelete()" class="comment-action-delete" i18n>Delete</div> |
46 | 46 | ||
47 | <my-user-moderation-dropdown | 47 | <my-user-moderation-dropdown |
48 | [prependActions]="prependModerationActions" | ||
48 | buttonSize="small" [account]="commentAccount" [user]="commentUser" i18n-label label="Options" placement="bottom-left auto" | 49 | buttonSize="small" [account]="commentAccount" [user]="commentUser" i18n-label label="Options" placement="bottom-left auto" |
49 | ></my-user-moderation-dropdown> | 50 | ></my-user-moderation-dropdown> |
50 | </div> | 51 | </div> |
@@ -93,3 +94,7 @@ | |||
93 | </div> | 94 | </div> |
94 | </div> | 95 | </div> |
95 | </div> | 96 | </div> |
97 | |||
98 | <ng-container *ngIf="prependModerationActions"> | ||
99 | <my-comment-report #commentReportModal [comment]="comment"></my-comment-report> | ||
100 | </ng-container> | ||
diff --git a/client/src/app/+videos/+video-watch/comment/video-comment.component.ts b/client/src/app/+videos/+video-watch/comment/video-comment.component.ts index 27846c1ad..6744a0954 100644 --- a/client/src/app/+videos/+video-watch/comment/video-comment.component.ts +++ b/client/src/app/+videos/+video-watch/comment/video-comment.component.ts | |||
@@ -1,10 +1,12 @@ | |||
1 | import { Component, EventEmitter, Input, OnChanges, OnInit, Output } from '@angular/core' | 1 | |
2 | import { Component, EventEmitter, Input, OnChanges, OnInit, Output, ViewChild } from '@angular/core' | ||
2 | import { MarkdownService, Notifier, UserService } from '@app/core' | 3 | import { MarkdownService, Notifier, UserService } from '@app/core' |
3 | import { AuthService } from '@app/core/auth' | 4 | import { AuthService } from '@app/core/auth' |
4 | import { Account, Actor, Video } from '@app/shared/shared-main' | 5 | import { Account, Actor, DropdownAction, Video } from '@app/shared/shared-main' |
6 | import { CommentReportComponent } from '@app/shared/shared-moderation/report-modals/comment-report.component' | ||
7 | import { VideoComment, VideoCommentThreadTree } from '@app/shared/shared-video-comment' | ||
8 | import { I18n } from '@ngx-translate/i18n-polyfill' | ||
5 | import { User, UserRight } from '@shared/models' | 9 | import { User, UserRight } from '@shared/models' |
6 | import { VideoCommentThreadTree } from './video-comment-thread-tree.model' | ||
7 | import { VideoComment } from './video-comment.model' | ||
8 | 10 | ||
9 | @Component({ | 11 | @Component({ |
10 | selector: 'my-video-comment', | 12 | selector: 'my-video-comment', |
@@ -12,6 +14,8 @@ import { VideoComment } from './video-comment.model' | |||
12 | styleUrls: ['./video-comment.component.scss'] | 14 | styleUrls: ['./video-comment.component.scss'] |
13 | }) | 15 | }) |
14 | export class VideoCommentComponent implements OnInit, OnChanges { | 16 | export class VideoCommentComponent implements OnInit, OnChanges { |
17 | @ViewChild('commentReportModal') commentReportModal: CommentReportComponent | ||
18 | |||
15 | @Input() video: Video | 19 | @Input() video: Video |
16 | @Input() comment: VideoComment | 20 | @Input() comment: VideoComment |
17 | @Input() parentComments: VideoComment[] = [] | 21 | @Input() parentComments: VideoComment[] = [] |
@@ -26,6 +30,8 @@ export class VideoCommentComponent implements OnInit, OnChanges { | |||
26 | @Output() resetReply = new EventEmitter() | 30 | @Output() resetReply = new EventEmitter() |
27 | @Output() timestampClicked = new EventEmitter<number>() | 31 | @Output() timestampClicked = new EventEmitter<number>() |
28 | 32 | ||
33 | prependModerationActions: DropdownAction<any>[] | ||
34 | |||
29 | sanitizedCommentHTML = '' | 35 | sanitizedCommentHTML = '' |
30 | newParentComments: VideoComment[] = [] | 36 | newParentComments: VideoComment[] = [] |
31 | 37 | ||
@@ -33,6 +39,7 @@ export class VideoCommentComponent implements OnInit, OnChanges { | |||
33 | commentUser: User | 39 | commentUser: User |
34 | 40 | ||
35 | constructor ( | 41 | constructor ( |
42 | private i18n: I18n, | ||
36 | private markdownService: MarkdownService, | 43 | private markdownService: MarkdownService, |
37 | private authService: AuthService, | 44 | private authService: AuthService, |
38 | private userService: UserService, | 45 | private userService: UserService, |
@@ -127,5 +134,20 @@ export class VideoCommentComponent implements OnInit, OnChanges { | |||
127 | } else { | 134 | } else { |
128 | this.comment.account = null | 135 | this.comment.account = null |
129 | } | 136 | } |
137 | |||
138 | if (this.isUserLoggedIn() && this.authService.getUser().account.id !== this.comment.account.id) { | ||
139 | this.prependModerationActions = [ | ||
140 | { | ||
141 | label: this.i18n('Report comment'), | ||
142 | handler: () => this.showReportModal() | ||
143 | } | ||
144 | ] | ||
145 | } else { | ||
146 | this.prependModerationActions = undefined | ||
147 | } | ||
148 | } | ||
149 | |||
150 | private showReportModal () { | ||
151 | this.commentReportModal.show() | ||
130 | } | 152 | } |
131 | } | 153 | } |
diff --git a/client/src/app/+videos/+video-watch/comment/video-comment.model.ts b/client/src/app/+videos/+video-watch/comment/video-comment.model.ts deleted file mode 100644 index e85443196..000000000 --- a/client/src/app/+videos/+video-watch/comment/video-comment.model.ts +++ /dev/null | |||
@@ -1,48 +0,0 @@ | |||
1 | import { getAbsoluteAPIUrl } from '@app/helpers' | ||
2 | import { Actor } from '@app/shared/shared-main' | ||
3 | import { Account as AccountInterface, VideoComment as VideoCommentServerModel } from '@shared/models' | ||
4 | |||
5 | export class VideoComment implements VideoCommentServerModel { | ||
6 | id: number | ||
7 | url: string | ||
8 | text: string | ||
9 | threadId: number | ||
10 | inReplyToCommentId: number | ||
11 | videoId: number | ||
12 | createdAt: Date | string | ||
13 | updatedAt: Date | string | ||
14 | deletedAt: Date | string | ||
15 | isDeleted: boolean | ||
16 | account: AccountInterface | ||
17 | totalRepliesFromVideoAuthor: number | ||
18 | totalReplies: number | ||
19 | by: string | ||
20 | accountAvatarUrl: string | ||
21 | |||
22 | isLocal: boolean | ||
23 | |||
24 | constructor (hash: VideoCommentServerModel) { | ||
25 | this.id = hash.id | ||
26 | this.url = hash.url | ||
27 | this.text = hash.text | ||
28 | this.threadId = hash.threadId | ||
29 | this.inReplyToCommentId = hash.inReplyToCommentId | ||
30 | this.videoId = hash.videoId | ||
31 | this.createdAt = new Date(hash.createdAt.toString()) | ||
32 | this.updatedAt = new Date(hash.updatedAt.toString()) | ||
33 | this.deletedAt = hash.deletedAt ? new Date(hash.deletedAt.toString()) : null | ||
34 | this.isDeleted = hash.isDeleted | ||
35 | this.account = hash.account | ||
36 | this.totalRepliesFromVideoAuthor = hash.totalRepliesFromVideoAuthor | ||
37 | this.totalReplies = hash.totalReplies | ||
38 | |||
39 | if (this.account) { | ||
40 | this.by = Actor.CREATE_BY_STRING(this.account.name, this.account.host) | ||
41 | this.accountAvatarUrl = Actor.GET_ACTOR_AVATAR_URL(this.account) | ||
42 | |||
43 | const absoluteAPIUrl = getAbsoluteAPIUrl() | ||
44 | const thisHost = new URL(absoluteAPIUrl).host | ||
45 | this.isLocal = this.account.host.trim() === thisHost | ||
46 | } | ||
47 | } | ||
48 | } | ||
diff --git a/client/src/app/+videos/+video-watch/comment/video-comment.service.ts b/client/src/app/+videos/+video-watch/comment/video-comment.service.ts deleted file mode 100644 index a73fb9ca8..000000000 --- a/client/src/app/+videos/+video-watch/comment/video-comment.service.ts +++ /dev/null | |||
@@ -1,149 +0,0 @@ | |||
1 | import { Observable } from 'rxjs' | ||
2 | import { catchError, map } from 'rxjs/operators' | ||
3 | import { HttpClient, HttpParams } from '@angular/common/http' | ||
4 | import { Injectable } from '@angular/core' | ||
5 | import { ComponentPaginationLight, RestExtractor, RestService } from '@app/core' | ||
6 | import { objectLineFeedToHtml } from '@app/helpers' | ||
7 | import { | ||
8 | FeedFormat, | ||
9 | ResultList, | ||
10 | VideoComment as VideoCommentServerModel, | ||
11 | VideoCommentCreate, | ||
12 | VideoCommentThreadTree as VideoCommentThreadTreeServerModel | ||
13 | } from '@shared/models' | ||
14 | import { environment } from '../../../../environments/environment' | ||
15 | import { VideoCommentThreadTree } from './video-comment-thread-tree.model' | ||
16 | import { VideoComment } from './video-comment.model' | ||
17 | |||
18 | @Injectable() | ||
19 | export class VideoCommentService { | ||
20 | private static BASE_VIDEO_URL = environment.apiUrl + '/api/v1/videos/' | ||
21 | private static BASE_FEEDS_URL = environment.apiUrl + '/feeds/video-comments.' | ||
22 | |||
23 | constructor ( | ||
24 | private authHttp: HttpClient, | ||
25 | private restExtractor: RestExtractor, | ||
26 | private restService: RestService | ||
27 | ) {} | ||
28 | |||
29 | addCommentThread (videoId: number | string, comment: VideoCommentCreate) { | ||
30 | const url = VideoCommentService.BASE_VIDEO_URL + videoId + '/comment-threads' | ||
31 | const normalizedComment = objectLineFeedToHtml(comment, 'text') | ||
32 | |||
33 | return this.authHttp.post<{ comment: VideoCommentServerModel }>(url, normalizedComment) | ||
34 | .pipe( | ||
35 | map(data => this.extractVideoComment(data.comment)), | ||
36 | catchError(err => this.restExtractor.handleError(err)) | ||
37 | ) | ||
38 | } | ||
39 | |||
40 | addCommentReply (videoId: number | string, inReplyToCommentId: number, comment: VideoCommentCreate) { | ||
41 | const url = VideoCommentService.BASE_VIDEO_URL + videoId + '/comments/' + inReplyToCommentId | ||
42 | const normalizedComment = objectLineFeedToHtml(comment, 'text') | ||
43 | |||
44 | return this.authHttp.post<{ comment: VideoCommentServerModel }>(url, normalizedComment) | ||
45 | .pipe( | ||
46 | map(data => this.extractVideoComment(data.comment)), | ||
47 | catchError(err => this.restExtractor.handleError(err)) | ||
48 | ) | ||
49 | } | ||
50 | |||
51 | getVideoCommentThreads (parameters: { | ||
52 | videoId: number | string, | ||
53 | componentPagination: ComponentPaginationLight, | ||
54 | sort: string | ||
55 | }): Observable<ResultList<VideoComment>> { | ||
56 | const { videoId, componentPagination, sort } = parameters | ||
57 | |||
58 | const pagination = this.restService.componentPaginationToRestPagination(componentPagination) | ||
59 | |||
60 | let params = new HttpParams() | ||
61 | params = this.restService.addRestGetParams(params, pagination, sort) | ||
62 | |||
63 | const url = VideoCommentService.BASE_VIDEO_URL + videoId + '/comment-threads' | ||
64 | return this.authHttp.get<ResultList<VideoComment>>(url, { params }) | ||
65 | .pipe( | ||
66 | map(result => this.extractVideoComments(result)), | ||
67 | catchError(err => this.restExtractor.handleError(err)) | ||
68 | ) | ||
69 | } | ||
70 | |||
71 | getVideoThreadComments (parameters: { | ||
72 | videoId: number | string, | ||
73 | threadId: number | ||
74 | }): Observable<VideoCommentThreadTree> { | ||
75 | const { videoId, threadId } = parameters | ||
76 | const url = `${VideoCommentService.BASE_VIDEO_URL + videoId}/comment-threads/${threadId}` | ||
77 | |||
78 | return this.authHttp | ||
79 | .get<VideoCommentThreadTreeServerModel>(url) | ||
80 | .pipe( | ||
81 | map(tree => this.extractVideoCommentTree(tree)), | ||
82 | catchError(err => this.restExtractor.handleError(err)) | ||
83 | ) | ||
84 | } | ||
85 | |||
86 | deleteVideoComment (videoId: number | string, commentId: number) { | ||
87 | const url = `${VideoCommentService.BASE_VIDEO_URL + videoId}/comments/${commentId}` | ||
88 | |||
89 | return this.authHttp | ||
90 | .delete(url) | ||
91 | .pipe( | ||
92 | map(this.restExtractor.extractDataBool), | ||
93 | catchError(err => this.restExtractor.handleError(err)) | ||
94 | ) | ||
95 | } | ||
96 | |||
97 | getVideoCommentsFeeds (videoUUID?: string) { | ||
98 | const feeds = [ | ||
99 | { | ||
100 | format: FeedFormat.RSS, | ||
101 | label: 'rss 2.0', | ||
102 | url: VideoCommentService.BASE_FEEDS_URL + FeedFormat.RSS.toLowerCase() | ||
103 | }, | ||
104 | { | ||
105 | format: FeedFormat.ATOM, | ||
106 | label: 'atom 1.0', | ||
107 | url: VideoCommentService.BASE_FEEDS_URL + FeedFormat.ATOM.toLowerCase() | ||
108 | }, | ||
109 | { | ||
110 | format: FeedFormat.JSON, | ||
111 | label: 'json 1.0', | ||
112 | url: VideoCommentService.BASE_FEEDS_URL + FeedFormat.JSON.toLowerCase() | ||
113 | } | ||
114 | ] | ||
115 | |||
116 | if (videoUUID !== undefined) { | ||
117 | for (const feed of feeds) { | ||
118 | feed.url += '?videoId=' + videoUUID | ||
119 | } | ||
120 | } | ||
121 | |||
122 | return feeds | ||
123 | } | ||
124 | |||
125 | private extractVideoComment (videoComment: VideoCommentServerModel) { | ||
126 | return new VideoComment(videoComment) | ||
127 | } | ||
128 | |||
129 | private extractVideoComments (result: ResultList<VideoCommentServerModel>) { | ||
130 | const videoCommentsJson = result.data | ||
131 | const totalComments = result.total | ||
132 | const comments: VideoComment[] = [] | ||
133 | |||
134 | for (const videoCommentJson of videoCommentsJson) { | ||
135 | comments.push(new VideoComment(videoCommentJson)) | ||
136 | } | ||
137 | |||
138 | return { data: comments, total: totalComments } | ||
139 | } | ||
140 | |||
141 | private extractVideoCommentTree (tree: VideoCommentThreadTreeServerModel) { | ||
142 | if (!tree) return tree as VideoCommentThreadTree | ||
143 | |||
144 | tree.comment = new VideoComment(tree.comment) | ||
145 | tree.children.forEach(c => this.extractVideoCommentTree(c)) | ||
146 | |||
147 | return tree as VideoCommentThreadTree | ||
148 | } | ||
149 | } | ||
diff --git a/client/src/app/+videos/+video-watch/comment/video-comments.component.ts b/client/src/app/+videos/+video-watch/comment/video-comments.component.ts index df0018ec6..66494a20a 100644 --- a/client/src/app/+videos/+video-watch/comment/video-comments.component.ts +++ b/client/src/app/+videos/+video-watch/comment/video-comments.component.ts | |||
@@ -4,10 +4,8 @@ import { ActivatedRoute } from '@angular/router' | |||
4 | import { AuthService, ComponentPagination, ConfirmService, hasMoreItems, Notifier, User } from '@app/core' | 4 | import { AuthService, ComponentPagination, ConfirmService, hasMoreItems, Notifier, User } from '@app/core' |
5 | import { HooksService } from '@app/core/plugins/hooks.service' | 5 | import { HooksService } from '@app/core/plugins/hooks.service' |
6 | import { Syndication, VideoDetails } from '@app/shared/shared-main' | 6 | import { Syndication, VideoDetails } from '@app/shared/shared-main' |
7 | import { VideoComment, VideoCommentService, VideoCommentThreadTree } from '@app/shared/shared-video-comment' | ||
7 | import { I18n } from '@ngx-translate/i18n-polyfill' | 8 | import { I18n } from '@ngx-translate/i18n-polyfill' |
8 | import { VideoCommentThreadTree } from './video-comment-thread-tree.model' | ||
9 | import { VideoComment } from './video-comment.model' | ||
10 | import { VideoCommentService } from './video-comment.service' | ||
11 | 9 | ||
12 | @Component({ | 10 | @Component({ |
13 | selector: 'my-video-comments', | 11 | selector: 'my-video-comments', |
diff --git a/client/src/app/+videos/+video-watch/video-watch.module.ts b/client/src/app/+videos/+video-watch/video-watch.module.ts index 421170d81..5821dc2b7 100644 --- a/client/src/app/+videos/+video-watch/video-watch.module.ts +++ b/client/src/app/+videos/+video-watch/video-watch.module.ts | |||
@@ -5,16 +5,17 @@ import { SharedGlobalIconModule } from '@app/shared/shared-icons' | |||
5 | import { SharedMainModule } from '@app/shared/shared-main' | 5 | import { SharedMainModule } from '@app/shared/shared-main' |
6 | import { SharedModerationModule } from '@app/shared/shared-moderation' | 6 | import { SharedModerationModule } from '@app/shared/shared-moderation' |
7 | import { SharedUserSubscriptionModule } from '@app/shared/shared-user-subscription' | 7 | import { SharedUserSubscriptionModule } from '@app/shared/shared-user-subscription' |
8 | import { SharedVideoCommentModule } from '@app/shared/shared-video-comment' | ||
8 | import { SharedVideoMiniatureModule } from '@app/shared/shared-video-miniature' | 9 | import { SharedVideoMiniatureModule } from '@app/shared/shared-video-miniature' |
9 | import { SharedVideoPlaylistModule } from '@app/shared/shared-video-playlist' | 10 | import { SharedVideoPlaylistModule } from '@app/shared/shared-video-playlist' |
10 | import { RecommendationsModule } from './recommendations/recommendations.module' | ||
11 | import { NgbTooltipModule } from '@ng-bootstrap/ng-bootstrap' | 11 | import { NgbTooltipModule } from '@ng-bootstrap/ng-bootstrap' |
12 | import { VideoCommentService } from '../../shared/shared-video-comment/video-comment.service' | ||
12 | import { VideoCommentAddComponent } from './comment/video-comment-add.component' | 13 | import { VideoCommentAddComponent } from './comment/video-comment-add.component' |
13 | import { VideoCommentComponent } from './comment/video-comment.component' | 14 | import { VideoCommentComponent } from './comment/video-comment.component' |
14 | import { VideoCommentService } from './comment/video-comment.service' | ||
15 | import { VideoCommentsComponent } from './comment/video-comments.component' | 15 | import { VideoCommentsComponent } from './comment/video-comments.component' |
16 | import { VideoShareComponent } from './modal/video-share.component' | 16 | import { VideoShareComponent } from './modal/video-share.component' |
17 | import { VideoSupportComponent } from './modal/video-support.component' | 17 | import { VideoSupportComponent } from './modal/video-support.component' |
18 | import { RecommendationsModule } from './recommendations/recommendations.module' | ||
18 | import { TimestampRouteTransformerDirective } from './timestamp-route-transformer.directive' | 19 | import { TimestampRouteTransformerDirective } from './timestamp-route-transformer.directive' |
19 | import { VideoDurationPipe } from './video-duration-formatter.pipe' | 20 | import { VideoDurationPipe } from './video-duration-formatter.pipe' |
20 | import { VideoWatchPlaylistComponent } from './video-watch-playlist.component' | 21 | import { VideoWatchPlaylistComponent } from './video-watch-playlist.component' |
@@ -34,7 +35,8 @@ import { VideoWatchComponent } from './video-watch.component' | |||
34 | SharedVideoPlaylistModule, | 35 | SharedVideoPlaylistModule, |
35 | SharedUserSubscriptionModule, | 36 | SharedUserSubscriptionModule, |
36 | SharedModerationModule, | 37 | SharedModerationModule, |
37 | SharedGlobalIconModule | 38 | SharedGlobalIconModule, |
39 | SharedVideoCommentModule | ||
38 | ], | 40 | ], |
39 | 41 | ||
40 | declarations: [ | 42 | declarations: [ |