diff options
author | Chocobozzz <me@florianbigard.com> | 2018-01-04 11:19:16 +0100 |
---|---|---|
committer | Chocobozzz <me@florianbigard.com> | 2018-01-04 11:19:16 +0100 |
commit | 4cb6d4578893db310297d7e118ce2fb7ecb952a3 (patch) | |
tree | a89a2e2062ba7bb91e922f07a7950ee51e090ccf /client/src | |
parent | cf117aaafc1e9ae1ab4c388fc5d2e5ba9349efee (diff) | |
download | PeerTube-4cb6d4578893db310297d7e118ce2fb7ecb952a3.tar.gz PeerTube-4cb6d4578893db310297d7e118ce2fb7ecb952a3.tar.zst PeerTube-4cb6d4578893db310297d7e118ce2fb7ecb952a3.zip |
Add ability to delete comments
Diffstat (limited to 'client/src')
10 files changed, 101 insertions, 22 deletions
diff --git a/client/src/app/app.component.ts b/client/src/app/app.component.ts index b1818c298..ef8597203 100644 --- a/client/src/app/app.component.ts +++ b/client/src/app/app.component.ts | |||
@@ -1,6 +1,6 @@ | |||
1 | import { Component, OnInit } from '@angular/core' | 1 | import { Component, OnInit } from '@angular/core' |
2 | import { Router } from '@angular/router' | 2 | import { Router } from '@angular/router' |
3 | import { AuthService, ServerService } from './core' | 3 | import { AuthService, ServerService } from '@app/core' |
4 | 4 | ||
5 | @Component({ | 5 | @Component({ |
6 | selector: 'my-app', | 6 | selector: 'my-app', |
@@ -50,10 +50,6 @@ export class AppComponent implements OnInit { | |||
50 | } | 50 | } |
51 | } | 51 | } |
52 | 52 | ||
53 | isInAdmin () { | ||
54 | return this.router.url.indexOf('/admin/') !== -1 | ||
55 | } | ||
56 | |||
57 | toggleMenu () { | 53 | toggleMenu () { |
58 | window.scrollTo(0, 0) | 54 | window.scrollTo(0, 0) |
59 | this.isMenuDisplayed = !this.isMenuDisplayed | 55 | this.isMenuDisplayed = !this.isMenuDisplayed |
diff --git a/client/src/app/shared/account/account.model.ts b/client/src/app/shared/account/account.model.ts index cc46dad77..1dce0003c 100644 --- a/client/src/app/shared/account/account.model.ts +++ b/client/src/app/shared/account/account.model.ts | |||
@@ -1,11 +1,11 @@ | |||
1 | import { Account as ServerAccount } from '../../../../../shared/models/actors/account.model' | 1 | import { Account as ServerAccount } from '../../../../../shared/models/actors/account.model' |
2 | import { Avatar } from '../../../../../shared/models/avatars/avatar.model' | 2 | import { Avatar } from '../../../../../shared/models/avatars/avatar.model' |
3 | import { environment } from '../../../environments/environment' | ||
4 | import { getAbsoluteAPIUrl } from '../misc/utils' | 3 | import { getAbsoluteAPIUrl } from '../misc/utils' |
5 | 4 | ||
6 | export class Account implements ServerAccount { | 5 | export class Account implements ServerAccount { |
7 | id: number | 6 | id: number |
8 | uuid: string | 7 | uuid: string |
8 | url: string | ||
9 | name: string | 9 | name: string |
10 | displayName: string | 10 | displayName: string |
11 | host: string | 11 | host: string |
diff --git a/client/src/app/videos/+video-edit/shared/video-edit.component.scss b/client/src/app/videos/+video-edit/shared/video-edit.component.scss index 0fefcee28..1df9d4006 100644 --- a/client/src/app/videos/+video-edit/shared/video-edit.component.scss +++ b/client/src/app/videos/+video-edit/shared/video-edit.component.scss | |||
@@ -51,8 +51,6 @@ | |||
51 | 51 | ||
52 | .submit-container { | 52 | .submit-container { |
53 | text-align: right; | 53 | text-align: right; |
54 | position: relative; | ||
55 | bottom: $button-height; | ||
56 | 54 | ||
57 | .message-submit { | 55 | .message-submit { |
58 | display: inline-block; | 56 | display: inline-block; |
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 e9c23929c..4f9597607 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 | |||
@@ -3,13 +3,14 @@ | |||
3 | 3 | ||
4 | <div class="comment"> | 4 | <div class="comment"> |
5 | <div class="comment-account-date"> | 5 | <div class="comment-account-date"> |
6 | <div class="comment-account">{{ comment.by }}</div> | 6 | <a target="_blank" [href]="comment.account.url" class="comment-account">{{ comment.by }}</a> |
7 | <div class="comment-date">{{ comment.createdAt | myFromNow }}</div> | 7 | <div class="comment-date">{{ comment.createdAt | myFromNow }}</div> |
8 | </div> | 8 | </div> |
9 | <div>{{ comment.text }}</div> | 9 | <div>{{ comment.text }}</div> |
10 | 10 | ||
11 | <div class="comment-actions"> | 11 | <div class="comment-actions"> |
12 | <div *ngIf="isUserLoggedIn()" (click)="onWantToReply()" class="comment-action-reply">Reply</div> | 12 | <div *ngIf="isUserLoggedIn()" (click)="onWantToReply()" class="comment-action-reply">Reply</div> |
13 | <div *ngIf="isRemovableByUser()" (click)="onWantToDelete()" class="comment-action-delete">Delete</div> | ||
13 | </div> | 14 | </div> |
14 | 15 | ||
15 | <my-video-comment-add | 16 | <my-video-comment-add |
@@ -28,7 +29,8 @@ | |||
28 | [video]="video" | 29 | [video]="video" |
29 | [inReplyToCommentId]="inReplyToCommentId" | 30 | [inReplyToCommentId]="inReplyToCommentId" |
30 | [commentTree]="commentChild" | 31 | [commentTree]="commentChild" |
31 | (wantedToReply)="onWantedToReply($event)" | 32 | (wantedToReply)="onWantToReply($event)" |
33 | (wantedToDelete)="onWantToDelete($event)" | ||
32 | (resetReply)="onResetReply()" | 34 | (resetReply)="onResetReply()" |
33 | ></my-video-comment> | 35 | ></my-video-comment> |
34 | </div> | 36 | </div> |
diff --git a/client/src/app/videos/+video-watch/comment/video-comment.component.scss b/client/src/app/videos/+video-watch/comment/video-comment.component.scss index aae03ab6d..a22c5a9fd 100644 --- a/client/src/app/videos/+video-watch/comment/video-comment.component.scss +++ b/client/src/app/videos/+video-watch/comment/video-comment.component.scss | |||
@@ -20,6 +20,9 @@ | |||
20 | margin-bottom: 4px; | 20 | margin-bottom: 4px; |
21 | 21 | ||
22 | .comment-account { | 22 | .comment-account { |
23 | @include disable-default-a-behaviour; | ||
24 | |||
25 | color: #000; | ||
23 | font-weight: $font-bold; | 26 | font-weight: $font-bold; |
24 | } | 27 | } |
25 | 28 | ||
@@ -31,10 +34,16 @@ | |||
31 | 34 | ||
32 | .comment-actions { | 35 | .comment-actions { |
33 | margin: 10px 0; | 36 | margin: 10px 0; |
37 | display: flex; | ||
34 | 38 | ||
35 | .comment-action-reply { | 39 | .comment-action-reply, .comment-action-delete { |
36 | color: #585858; | 40 | color: #585858; |
37 | cursor: pointer; | 41 | cursor: pointer; |
42 | margin-right: 10px; | ||
43 | |||
44 | &:hover { | ||
45 | color: #000; | ||
46 | } | ||
38 | } | 47 | } |
39 | } | 48 | } |
40 | } | 49 | } |
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 b305c639a..9bc9c8844 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,5 +1,6 @@ | |||
1 | import { Component, EventEmitter, Input, Output } from '@angular/core' | 1 | import { Component, EventEmitter, Input, Output } from '@angular/core' |
2 | import { Account as AccountInterface } from '../../../../../../shared/models/actors' | 2 | import { Account as AccountInterface } from '../../../../../../shared/models/actors' |
3 | import { UserRight } from '../../../../../../shared/models/users' | ||
3 | import { VideoCommentThreadTree } from '../../../../../../shared/models/videos/video-comment.model' | 4 | import { VideoCommentThreadTree } from '../../../../../../shared/models/videos/video-comment.model' |
4 | import { AuthService } from '../../../core/auth' | 5 | import { AuthService } from '../../../core/auth' |
5 | import { Account } from '../../../shared/account/account.model' | 6 | import { Account } from '../../../shared/account/account.model' |
@@ -17,7 +18,9 @@ export class VideoCommentComponent { | |||
17 | @Input() commentTree: VideoCommentThreadTree | 18 | @Input() commentTree: VideoCommentThreadTree |
18 | @Input() inReplyToCommentId: number | 19 | @Input() inReplyToCommentId: number |
19 | 20 | ||
21 | @Output() wantedToDelete = new EventEmitter<VideoComment>() | ||
20 | @Output() wantedToReply = new EventEmitter<VideoComment>() | 22 | @Output() wantedToReply = new EventEmitter<VideoComment>() |
23 | @Output() threadCreated = new EventEmitter<VideoCommentThreadTree>() | ||
21 | @Output() resetReply = new EventEmitter() | 24 | @Output() resetReply = new EventEmitter() |
22 | 25 | ||
23 | constructor (private authService: AuthService) {} | 26 | constructor (private authService: AuthService) {} |
@@ -32,6 +35,8 @@ export class VideoCommentComponent { | |||
32 | comment: this.comment, | 35 | comment: this.comment, |
33 | children: [] | 36 | children: [] |
34 | } | 37 | } |
38 | |||
39 | this.threadCreated.emit(this.commentTree) | ||
35 | } | 40 | } |
36 | 41 | ||
37 | this.commentTree.children.push({ | 42 | this.commentTree.children.push({ |
@@ -41,17 +46,16 @@ export class VideoCommentComponent { | |||
41 | this.resetReply.emit() | 46 | this.resetReply.emit() |
42 | } | 47 | } |
43 | 48 | ||
44 | onWantToReply () { | 49 | onWantToReply (comment?: VideoComment) { |
45 | this.wantedToReply.emit(this.comment) | 50 | this.wantedToReply.emit(comment || this.comment) |
46 | } | 51 | } |
47 | 52 | ||
48 | isUserLoggedIn () { | 53 | onWantToDelete (comment?: VideoComment) { |
49 | return this.authService.isLoggedIn() | 54 | this.wantedToDelete.emit(comment || this.comment) |
50 | } | 55 | } |
51 | 56 | ||
52 | // Event from child comment | 57 | isUserLoggedIn () { |
53 | onWantedToReply (comment: VideoComment) { | 58 | return this.authService.isLoggedIn() |
54 | this.wantedToReply.emit(comment) | ||
55 | } | 59 | } |
56 | 60 | ||
57 | onResetReply () { | 61 | onResetReply () { |
@@ -61,4 +65,12 @@ export class VideoCommentComponent { | |||
61 | getAvatarUrl (account: AccountInterface) { | 65 | getAvatarUrl (account: AccountInterface) { |
62 | return Account.GET_ACCOUNT_AVATAR_URL(account) | 66 | return Account.GET_ACCOUNT_AVATAR_URL(account) |
63 | } | 67 | } |
68 | |||
69 | isRemovableByUser () { | ||
70 | return this.isUserLoggedIn() && | ||
71 | ( | ||
72 | this.user.account.id === this.comment.account.id || | ||
73 | this.user.hasRight(UserRight.REMOVE_ANY_VIDEO_COMMENT) | ||
74 | ) | ||
75 | } | ||
64 | } | 76 | } |
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 index 2fe6cc3e9..c42f55496 100644 --- a/client/src/app/videos/+video-watch/comment/video-comment.service.ts +++ b/client/src/app/videos/+video-watch/comment/video-comment.service.ts | |||
@@ -66,6 +66,15 @@ export class VideoCommentService { | |||
66 | .catch((res) => this.restExtractor.handleError(res)) | 66 | .catch((res) => this.restExtractor.handleError(res)) |
67 | } | 67 | } |
68 | 68 | ||
69 | deleteVideoComment (videoId: number | string, commentId: number) { | ||
70 | const url = `${VideoCommentService.BASE_VIDEO_URL + videoId}/comments/${commentId}` | ||
71 | |||
72 | return this.authHttp | ||
73 | .delete(url) | ||
74 | .map(this.restExtractor.extractDataBool) | ||
75 | .catch((res) => this.restExtractor.handleError(res)) | ||
76 | } | ||
77 | |||
69 | private extractVideoComment (videoComment: VideoCommentServerModel) { | 78 | private extractVideoComment (videoComment: VideoCommentServerModel) { |
70 | return new VideoComment(videoComment) | 79 | return new VideoComment(videoComment) |
71 | } | 80 | } |
diff --git a/client/src/app/videos/+video-watch/comment/video-comments.component.html b/client/src/app/videos/+video-watch/comment/video-comments.component.html index 4a4248073..80b200931 100644 --- a/client/src/app/videos/+video-watch/comment/video-comments.component.html +++ b/client/src/app/videos/+video-watch/comment/video-comments.component.html | |||
@@ -27,6 +27,8 @@ | |||
27 | [inReplyToCommentId]="inReplyToCommentId" | 27 | [inReplyToCommentId]="inReplyToCommentId" |
28 | [commentTree]="threadComments[comment.id]" | 28 | [commentTree]="threadComments[comment.id]" |
29 | (wantedToReply)="onWantedToReply($event)" | 29 | (wantedToReply)="onWantedToReply($event)" |
30 | (wantedToDelete)="onWantedToDelete($event)" | ||
31 | (threadCreated)="onThreadCreated($event)" | ||
30 | (resetReply)="onResetReply()" | 32 | (resetReply)="onResetReply()" |
31 | ></my-video-comment> | 33 | ></my-video-comment> |
32 | 34 | ||
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 1230725c1..030dee9af 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 | |||
@@ -1,6 +1,7 @@ | |||
1 | import { Component, Input, OnInit } from '@angular/core' | 1 | import { Component, Input, OnInit } from '@angular/core' |
2 | import { ConfirmService } from '@app/core' | ||
2 | import { NotificationsService } from 'angular2-notifications' | 3 | import { NotificationsService } from 'angular2-notifications' |
3 | import { VideoCommentThreadTree } from '../../../../../../shared/models/videos/video-comment.model' | 4 | import { VideoComment as VideoCommentInterface, VideoCommentThreadTree } from '../../../../../../shared/models/videos/video-comment.model' |
4 | import { AuthService } from '../../../core/auth' | 5 | import { AuthService } from '../../../core/auth' |
5 | import { ComponentPagination } from '../../../shared/rest/component-pagination.model' | 6 | import { ComponentPagination } from '../../../shared/rest/component-pagination.model' |
6 | import { User } from '../../../shared/users' | 7 | import { User } from '../../../shared/users' |
@@ -32,6 +33,7 @@ export class VideoCommentsComponent implements OnInit { | |||
32 | constructor ( | 33 | constructor ( |
33 | private authService: AuthService, | 34 | private authService: AuthService, |
34 | private notificationsService: NotificationsService, | 35 | private notificationsService: NotificationsService, |
36 | private confirmService: ConfirmService, | ||
35 | private videoCommentService: VideoCommentService | 37 | private videoCommentService: VideoCommentService |
36 | ) {} | 38 | ) {} |
37 | 39 | ||
@@ -41,7 +43,7 @@ export class VideoCommentsComponent implements OnInit { | |||
41 | } | 43 | } |
42 | } | 44 | } |
43 | 45 | ||
44 | viewReplies (comment: VideoComment) { | 46 | viewReplies (comment: VideoCommentInterface) { |
45 | this.threadLoading[comment.id] = true | 47 | this.threadLoading[comment.id] = true |
46 | 48 | ||
47 | this.videoCommentService.getVideoThreadComments(this.video.id, comment.id) | 49 | this.videoCommentService.getVideoThreadComments(this.video.id, comment.id) |
@@ -79,6 +81,44 @@ export class VideoCommentsComponent implements OnInit { | |||
79 | this.inReplyToCommentId = undefined | 81 | this.inReplyToCommentId = undefined |
80 | } | 82 | } |
81 | 83 | ||
84 | onThreadCreated (commentTree: VideoCommentThreadTree) { | ||
85 | this.viewReplies(commentTree.comment) | ||
86 | } | ||
87 | |||
88 | onWantedToDelete (commentToDelete: VideoComment) { | ||
89 | let message = 'Do you really want to delete this comment?' | ||
90 | if (commentToDelete.totalReplies !== 0) message += `${commentToDelete.totalReplies} would be deleted too.` | ||
91 | |||
92 | this.confirmService.confirm(message, 'Delete').subscribe( | ||
93 | res => { | ||
94 | if (res === false) return | ||
95 | |||
96 | this.videoCommentService.deleteVideoComment(commentToDelete.videoId, commentToDelete.id) | ||
97 | .subscribe( | ||
98 | () => { | ||
99 | // Delete the comment in the tree | ||
100 | if (commentToDelete.inReplyToCommentId) { | ||
101 | const thread = this.threadComments[commentToDelete.threadId] | ||
102 | if (!thread) { | ||
103 | console.error(`Cannot find thread ${commentToDelete.threadId} of the comment to delete ${commentToDelete.id}`) | ||
104 | return | ||
105 | } | ||
106 | |||
107 | this.deleteLocalCommentThread(thread, commentToDelete) | ||
108 | return | ||
109 | } | ||
110 | |||
111 | // Delete the thread | ||
112 | this.comments = this.comments.filter(c => c.id !== commentToDelete.id) | ||
113 | this.componentPagination.totalItems-- | ||
114 | }, | ||
115 | |||
116 | err => this.notificationsService.error('Error', err.message) | ||
117 | ) | ||
118 | } | ||
119 | ) | ||
120 | } | ||
121 | |||
82 | isUserLoggedIn () { | 122 | isUserLoggedIn () { |
83 | return this.authService.isLoggedIn() | 123 | return this.authService.isLoggedIn() |
84 | } | 124 | } |
@@ -91,7 +131,7 @@ export class VideoCommentsComponent implements OnInit { | |||
91 | } | 131 | } |
92 | } | 132 | } |
93 | 133 | ||
94 | protected hasMoreComments () { | 134 | private hasMoreComments () { |
95 | // No results | 135 | // No results |
96 | if (this.componentPagination.totalItems === 0) return false | 136 | if (this.componentPagination.totalItems === 0) return false |
97 | 137 | ||
@@ -101,4 +141,15 @@ export class VideoCommentsComponent implements OnInit { | |||
101 | const maxPage = this.componentPagination.totalItems / this.componentPagination.itemsPerPage | 141 | const maxPage = this.componentPagination.totalItems / this.componentPagination.itemsPerPage |
102 | return maxPage > this.componentPagination.currentPage | 142 | return maxPage > this.componentPagination.currentPage |
103 | } | 143 | } |
144 | |||
145 | private deleteLocalCommentThread (parentComment: VideoCommentThreadTree, commentToDelete: VideoComment) { | ||
146 | for (const commentChild of parentComment.children) { | ||
147 | if (commentChild.comment.id === commentToDelete.id) { | ||
148 | parentComment.children = parentComment.children.filter(c => c.comment.id !== commentToDelete.id) | ||
149 | return | ||
150 | } | ||
151 | |||
152 | this.deleteLocalCommentThread(commentChild, commentToDelete) | ||
153 | } | ||
154 | } | ||
104 | } | 155 | } |
diff --git a/client/src/app/videos/+video-watch/video-watch.component.ts b/client/src/app/videos/+video-watch/video-watch.component.ts index 0f44d3dd7..f1f194764 100644 --- a/client/src/app/videos/+video-watch/video-watch.component.ts +++ b/client/src/app/videos/+video-watch/video-watch.component.ts | |||
@@ -137,7 +137,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy { | |||
137 | blacklistVideo (event: Event) { | 137 | blacklistVideo (event: Event) { |
138 | event.preventDefault() | 138 | event.preventDefault() |
139 | 139 | ||
140 | this.confirmService.confirm('Do you really want to blacklist this video ?', 'Blacklist').subscribe( | 140 | this.confirmService.confirm('Do you really want to blacklist this video?', 'Blacklist').subscribe( |
141 | res => { | 141 | res => { |
142 | if (res === false) return | 142 | if (res === false) return |
143 | 143 | ||