diff options
author | Julien Maulny <julien.maulny@protonmail.com> | 2019-11-15 19:05:08 +0100 |
---|---|---|
committer | Chocobozzz <chocobozzz@cpy.re> | 2019-12-04 09:36:45 +0100 |
commit | 69222afac8f8c41d90295b33f0695bbff352851e (patch) | |
tree | 63fe1faea94dd3bfc54e633631eecb275c969e54 /client/src/app | |
parent | 69c7f7525ddf13b7ced787d8b72ac74b43665517 (diff) | |
download | PeerTube-69222afac8f8c41d90295b33f0695bbff352851e.tar.gz PeerTube-69222afac8f8c41d90295b33f0695bbff352851e.tar.zst PeerTube-69222afac8f8c41d90295b33f0695bbff352851e.zip |
Soft delete video comments instead of detroy
Diffstat (limited to 'client/src/app')
5 files changed, 62 insertions, 49 deletions
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 60b803206..6ec35d63b 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 | |||
@@ -1,22 +1,45 @@ | |||
1 | <div class="root-comment"> | 1 | <div class="root-comment"> |
2 | <img [src]="comment.accountAvatarUrl" alt="Avatar" /> | 2 | <img |
3 | *ngIf="!comment.isDeleted" | ||
4 | class="comment-avatar" | ||
5 | [src]="comment.accountAvatarUrl" | ||
6 | alt="Avatar" | ||
7 | /> | ||
8 | |||
9 | <span | ||
10 | *ngIf="comment.isDeleted" | ||
11 | class="comment-avatar" | ||
12 | ></span> | ||
3 | 13 | ||
4 | <div class="comment"> | 14 | <div class="comment"> |
5 | <div *ngIf="highlightedComment === true" class="highlighted-comment" i18n>Highlighted comment</div> | 15 | <ng-container *ngIf="!comment.isDeleted"> |
16 | <div *ngIf="highlightedComment === true" class="highlighted-comment" i18n>Highlighted comment</div> | ||
6 | 17 | ||
7 | <div class="comment-account-date"> | 18 | <div class="comment-account-date"> |
8 | <a [href]="comment.account.url" target="_blank" rel="noopener noreferrer" class="comment-account">{{ comment.by }}</a> | 19 | <a [href]="comment.account.url" target="_blank" rel="noopener noreferrer" class="comment-account">{{ comment.by }}</a> |
9 | <a [routerLink]="['/videos/watch', video.uuid, { 'threadId': comment.threadId }]" class="comment-date">{{ comment.createdAt | myFromNow }}</a> | 20 | <a [routerLink]="['/videos/watch', video.uuid, { 'threadId': comment.threadId }]" class="comment-date">{{ comment.createdAt | myFromNow }}</a> |
10 | </div> | 21 | </div> |
11 | <div class="comment-html" [innerHTML]="sanitizedCommentHTML"></div> | 22 | <div class="comment-html" [innerHTML]="sanitizedCommentHTML"></div> |
12 | 23 | ||
13 | <div class="comment-actions"> | 24 | <div class="comment-actions"> |
14 | <div *ngIf="isUserLoggedIn()" (click)="onWantToReply()" class="comment-action-reply" i18n>Reply</div> | 25 | <div *ngIf="isUserLoggedIn()" (click)="onWantToReply()" class="comment-action-reply" i18n>Reply</div> |
15 | <div *ngIf="isRemovableByUser()" (click)="onWantToDelete()" class="comment-action-delete" i18n>Delete</div> | 26 | <div *ngIf="isRemovableByUser()" (click)="onWantToDelete()" class="comment-action-delete" i18n>Delete</div> |
16 | </div> | 27 | </div> |
28 | </ng-container> | ||
29 | |||
30 | <ng-container *ngIf="comment.isDeleted"> | ||
31 | <div class="comment-account-date"> | ||
32 | <span class="comment-account" i18n>Deleted</span> | ||
33 | <a [routerLink]="['/videos/watch', video.uuid, { 'threadId': comment.threadId }]" class="comment-date">{{ comment.createdAt | myFromNow }}</a> | ||
34 | </div> | ||
35 | |||
36 | <div *ngIf="comment.isDeleted" class="comment-html comment-html-deleted"> | ||
37 | <i i18n>This comment has been deleted</i> | ||
38 | </div> | ||
39 | </ng-container> | ||
17 | 40 | ||
18 | <my-video-comment-add | 41 | <my-video-comment-add |
19 | *ngIf="isUserLoggedIn() && inReplyToCommentId === comment.id" | 42 | *ngIf="!comment.isDeleted && isUserLoggedIn() && inReplyToCommentId === comment.id" |
20 | [user]="user" | 43 | [user]="user" |
21 | [video]="video" | 44 | [video]="video" |
22 | [parentComment]="comment" | 45 | [parentComment]="comment" |
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 c3ab1ab01..ac633fd64 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 | |||
@@ -5,7 +5,7 @@ | |||
5 | font-size: 15px; | 5 | font-size: 15px; |
6 | display: flex; | 6 | display: flex; |
7 | 7 | ||
8 | img { | 8 | .comment-avatar { |
9 | @include avatar(36px); | 9 | @include avatar(36px); |
10 | 10 | ||
11 | margin-top: 5px; | 11 | margin-top: 5px; |
@@ -48,6 +48,7 @@ | |||
48 | 48 | ||
49 | .comment-html { | 49 | .comment-html { |
50 | @include peertube-word-wrap; | 50 | @include peertube-word-wrap; |
51 | margin-bottom: 10px; | ||
51 | 52 | ||
52 | // Mentions | 53 | // Mentions |
53 | ::ng-deep a { | 54 | ::ng-deep a { |
@@ -61,10 +62,14 @@ | |||
61 | } | 62 | } |
62 | 63 | ||
63 | } | 64 | } |
65 | |||
66 | &.comment-html-deleted { | ||
67 | color: $grey-foreground-color; | ||
68 | } | ||
64 | } | 69 | } |
65 | 70 | ||
66 | .comment-actions { | 71 | .comment-actions { |
67 | margin: 10px 0; | 72 | margin-bottom: 10px; |
68 | display: flex; | 73 | display: flex; |
69 | 74 | ||
70 | .comment-action-reply, | 75 | .comment-action-reply, |
@@ -100,7 +105,7 @@ | |||
100 | } | 105 | } |
101 | 106 | ||
102 | .root-comment { | 107 | .root-comment { |
103 | img { margin-right: 10px; } | 108 | .comment-avatar { margin-right: 10px; } |
104 | } | 109 | } |
105 | } | 110 | } |
106 | 111 | ||
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 172eb0a39..4d3c049a1 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 | |||
@@ -78,7 +78,7 @@ export class VideoCommentComponent implements OnInit, OnChanges { | |||
78 | } | 78 | } |
79 | 79 | ||
80 | isRemovableByUser () { | 80 | isRemovableByUser () { |
81 | return this.isUserLoggedIn() && | 81 | return this.comment.account && this.isUserLoggedIn() && |
82 | ( | 82 | ( |
83 | this.user.account.id === this.comment.account.id || | 83 | this.user.account.id === this.comment.account.id || |
84 | this.user.hasRight(UserRight.REMOVE_ANY_VIDEO_COMMENT) | 84 | this.user.hasRight(UserRight.REMOVE_ANY_VIDEO_COMMENT) |
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 index 3ed3ddcc7..719d1f04e 100644 --- a/client/src/app/videos/+video-watch/comment/video-comment.model.ts +++ b/client/src/app/videos/+video-watch/comment/video-comment.model.ts | |||
@@ -12,6 +12,8 @@ export class VideoComment implements VideoCommentServerModel { | |||
12 | videoId: number | 12 | videoId: number |
13 | createdAt: Date | string | 13 | createdAt: Date | string |
14 | updatedAt: Date | string | 14 | updatedAt: Date | string |
15 | deletedAt: Date | string | ||
16 | isDeleted: boolean | ||
15 | account: AccountInterface | 17 | account: AccountInterface |
16 | totalReplies: number | 18 | totalReplies: number |
17 | by: string | 19 | by: string |
@@ -28,14 +30,18 @@ export class VideoComment implements VideoCommentServerModel { | |||
28 | this.videoId = hash.videoId | 30 | this.videoId = hash.videoId |
29 | this.createdAt = new Date(hash.createdAt.toString()) | 31 | this.createdAt = new Date(hash.createdAt.toString()) |
30 | this.updatedAt = new Date(hash.updatedAt.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 | ||
31 | this.account = hash.account | 35 | this.account = hash.account |
32 | this.totalReplies = hash.totalReplies | 36 | this.totalReplies = hash.totalReplies |
33 | 37 | ||
34 | this.by = Actor.CREATE_BY_STRING(this.account.name, this.account.host) | 38 | if (this.account) { |
35 | this.accountAvatarUrl = Actor.GET_ACTOR_AVATAR_URL(this.account) | 39 | this.by = Actor.CREATE_BY_STRING(this.account.name, this.account.host) |
40 | this.accountAvatarUrl = Actor.GET_ACTOR_AVATAR_URL(this.account) | ||
36 | 41 | ||
37 | const absoluteAPIUrl = getAbsoluteAPIUrl() | 42 | const absoluteAPIUrl = getAbsoluteAPIUrl() |
38 | const thisHost = new URL(absoluteAPIUrl).host | 43 | const thisHost = new URL(absoluteAPIUrl).host |
39 | this.isLocal = this.account.host.trim() === thisHost | 44 | this.isLocal = this.account.host.trim() === thisHost |
45 | } | ||
40 | } | 46 | } |
41 | } | 47 | } |
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 57b98afce..cc8b98b4e 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 | |||
@@ -153,10 +153,6 @@ export class VideoCommentsComponent implements OnInit, OnChanges, OnDestroy { | |||
153 | async onWantedToDelete (commentToDelete: VideoComment) { | 153 | async onWantedToDelete (commentToDelete: VideoComment) { |
154 | let message = 'Do you really want to delete this comment?' | 154 | let message = 'Do you really want to delete this comment?' |
155 | 155 | ||
156 | if (commentToDelete.totalReplies !== 0) { | ||
157 | message += this.i18n(' {{totalReplies}} replies will be deleted too.', { totalReplies: commentToDelete.totalReplies }) | ||
158 | } | ||
159 | |||
160 | if (commentToDelete.isLocal) { | 156 | if (commentToDelete.isLocal) { |
161 | message += this.i18n(' The deletion will be sent to remote instances, so they remove the comment too.') | 157 | message += this.i18n(' The deletion will be sent to remote instances, so they remove the comment too.') |
162 | } else { | 158 | } else { |
@@ -169,21 +165,8 @@ export class VideoCommentsComponent implements OnInit, OnChanges, OnDestroy { | |||
169 | this.videoCommentService.deleteVideoComment(commentToDelete.videoId, commentToDelete.id) | 165 | this.videoCommentService.deleteVideoComment(commentToDelete.videoId, commentToDelete.id) |
170 | .subscribe( | 166 | .subscribe( |
171 | () => { | 167 | () => { |
172 | // Delete the comment in the tree | 168 | // Mark the comment as deleted |
173 | if (commentToDelete.inReplyToCommentId) { | 169 | this.softDeleteComment(commentToDelete) |
174 | const thread = this.threadComments[commentToDelete.threadId] | ||
175 | if (!thread) { | ||
176 | console.error(`Cannot find thread ${commentToDelete.threadId} of the comment to delete ${commentToDelete.id}`) | ||
177 | return | ||
178 | } | ||
179 | |||
180 | this.deleteLocalCommentThread(thread, commentToDelete) | ||
181 | return | ||
182 | } | ||
183 | |||
184 | // Delete the thread | ||
185 | this.comments = this.comments.filter(c => c.id !== commentToDelete.id) | ||
186 | this.componentPagination.totalItems-- | ||
187 | 170 | ||
188 | if (this.highlightedThread.id === commentToDelete.id) this.highlightedThread = undefined | 171 | if (this.highlightedThread.id === commentToDelete.id) this.highlightedThread = undefined |
189 | }, | 172 | }, |
@@ -204,15 +187,11 @@ export class VideoCommentsComponent implements OnInit, OnChanges, OnDestroy { | |||
204 | } | 187 | } |
205 | } | 188 | } |
206 | 189 | ||
207 | private deleteLocalCommentThread (parentComment: VideoCommentThreadTree, commentToDelete: VideoComment) { | 190 | private softDeleteComment (comment: VideoComment) { |
208 | for (const commentChild of parentComment.children) { | 191 | comment.isDeleted = true |
209 | if (commentChild.comment.id === commentToDelete.id) { | 192 | comment.deletedAt = new Date() |
210 | parentComment.children = parentComment.children.filter(c => c.comment.id !== commentToDelete.id) | 193 | comment.text = '' |
211 | return | 194 | comment.account = null |
212 | } | ||
213 | |||
214 | this.deleteLocalCommentThread(commentChild, commentToDelete) | ||
215 | } | ||
216 | } | 195 | } |
217 | 196 | ||
218 | private resetVideo () { | 197 | private resetVideo () { |