diff --git a/app/Activity/CommentRepo.php b/app/Activity/CommentRepo.php
index c488350ca..866368ee6 100644
--- a/app/Activity/CommentRepo.php
+++ b/app/Activity/CommentRepo.php
@@ -53,6 +53,33 @@ class CommentRepo
return $comment;
}
+
+ /**
+ * Archive an existing comment.
+ */
+ public function archive(Comment $comment): Comment
+ {
+ $comment->archived = true;
+ $comment->save();
+
+ ActivityService::add(ActivityType::COMMENT_UPDATE, $comment);
+
+ return $comment;
+ }
+
+ /**
+ * Un-archive an existing comment.
+ */
+ public function unarchive(Comment $comment): Comment
+ {
+ $comment->archived = false;
+ $comment->save();
+
+ ActivityService::add(ActivityType::COMMENT_UPDATE, $comment);
+
+ return $comment;
+ }
+
/**
* Delete a comment from the system.
*/
diff --git a/app/Activity/Controllers/CommentController.php b/app/Activity/Controllers/CommentController.php
index 262080067..7a290ebab 100644
--- a/app/Activity/Controllers/CommentController.php
+++ b/app/Activity/Controllers/CommentController.php
@@ -75,6 +75,42 @@ class CommentController extends Controller
]);
}
+ /**
+ * Mark a comment as archived.
+ */
+ public function archive(int $id)
+ {
+ $comment = $this->commentRepo->getById($id);
+ if (!userCan('comment-update', $comment) && !userCan('comment-delete', $comment)) {
+ $this->showPermissionError();
+ }
+
+ $this->commentRepo->archive($comment);
+
+ return view('comments.comment', [
+ 'comment' => $comment,
+ 'readOnly' => false,
+ ]);
+ }
+
+ /**
+ * Unmark a comment as archived.
+ */
+ public function unarchive(int $id)
+ {
+ $comment = $this->commentRepo->getById($id);
+ if (!userCan('comment-update', $comment) && !userCan('comment-delete', $comment)) {
+ $this->showPermissionError();
+ }
+
+ $this->commentRepo->unarchive($comment);
+
+ return view('comments.comment', [
+ 'comment' => $comment,
+ 'readOnly' => false,
+ ]);
+ }
+
/**
* Delete a comment from the system.
*/
diff --git a/lang/en/common.php b/lang/en/common.php
index b05169bb2..06a9e855c 100644
--- a/lang/en/common.php
+++ b/lang/en/common.php
@@ -30,6 +30,8 @@ return [
'create' => 'Create',
'update' => 'Update',
'edit' => 'Edit',
+ 'archive' => 'Archive',
+ 'unarchive' => 'Un-Archive',
'sort' => 'Sort',
'move' => 'Move',
'copy' => 'Copy',
diff --git a/lang/en/entities.php b/lang/en/entities.php
index f9fab8ebf..141e75b5f 100644
--- a/lang/en/entities.php
+++ b/lang/en/entities.php
@@ -402,6 +402,8 @@ return [
'comment_deleted_success' => 'Comment deleted',
'comment_created_success' => 'Comment added',
'comment_updated_success' => 'Comment updated',
+ 'comment_archive_success' => 'Comment archived',
+ 'comment_unarchive_success' => 'Comment un-archived',
'comment_view' => 'View comment',
'comment_jump_to_thread' => 'Jump to thread',
'comment_delete_confirm' => 'Are you sure you want to delete this comment?',
diff --git a/resources/icons/archive.svg b/resources/icons/archive.svg
new file mode 100644
index 000000000..90a4f35b7
--- /dev/null
+++ b/resources/icons/archive.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/resources/js/components/page-comment.ts b/resources/js/components/page-comment.ts
index 11ad769b1..d2cbd21d1 100644
--- a/resources/js/components/page-comment.ts
+++ b/resources/js/components/page-comment.ts
@@ -8,6 +8,7 @@ export class PageComment extends Component {
protected commentLocalId: string;
protected deletedText: string;
protected updatedText: string;
+ protected archiveText: string;
protected wysiwygEditor: any = null;
protected wysiwygLanguage: string;
@@ -20,6 +21,7 @@ export class PageComment extends Component {
protected editButton: HTMLElement;
protected deleteButton: HTMLElement;
protected replyButton: HTMLElement;
+ protected archiveButton: HTMLElement;
protected input: HTMLInputElement;
setup() {
@@ -27,7 +29,8 @@ export class PageComment extends Component {
this.commentId = this.$opts.commentId;
this.commentLocalId = this.$opts.commentLocalId;
this.deletedText = this.$opts.deletedText;
- this.updatedText = this.$opts.updatedText;
+ this.deletedText = this.$opts.deletedText;
+ this.archiveText = this.$opts.archiveText;
// Editor reference and text options
this.wysiwygLanguage = this.$opts.wysiwygLanguage;
@@ -41,6 +44,7 @@ export class PageComment extends Component {
this.editButton = this.$refs.editButton;
this.deleteButton = this.$refs.deleteButton;
this.replyButton = this.$refs.replyButton;
+ this.archiveButton = this.$refs.archiveButton;
this.input = this.$refs.input as HTMLInputElement;
this.setupListeners();
@@ -63,6 +67,10 @@ export class PageComment extends Component {
if (this.deleteButton) {
this.deleteButton.addEventListener('click', this.delete.bind(this));
}
+
+ if (this.archiveButton) {
+ this.archiveButton.addEventListener('click', this.archive.bind(this));
+ }
}
protected toggleEditMode(show: boolean) : void {
@@ -126,6 +134,15 @@ export class PageComment extends Component {
window.$events.success(this.deletedText);
}
+ protected async archive(): Promise {
+ this.showLoading();
+ const isArchived = this.archiveButton.dataset.isArchived === 'true';
+
+ await window.$http.put(`/comment/${this.commentId}/${isArchived ? 'unarchive' : 'archive'}`);
+ this.$emit('archive');
+ window.$events.success(this.archiveText);
+ }
+
protected showLoading(): HTMLElement {
const loading = getLoading();
loading.classList.add('px-l');
diff --git a/resources/views/comments/comment.blade.php b/resources/views/comments/comment.blade.php
index 5310b2fe4..58e057140 100644
--- a/resources/views/comments/comment.blade.php
+++ b/resources/views/comments/comment.blade.php
@@ -6,6 +6,7 @@
option:page-comment:comment-local-id="{{ $comment->local_id }}"
option:page-comment:updated-text="{{ trans('entities.comment_updated_success') }}"
option:page-comment:deleted-text="{{ trans('entities.comment_deleted_success') }}"
+ option:page-comment:archive-text="{{ $comment->archived ? trans('entities.comment_unarchive_success') : trans('entities.comment_archive_success') }}"
option:page-comment:wysiwyg-language="{{ $locale->htmlLang() }}"
option:page-comment:wysiwyg-text-direction="{{ $locale->htmlDirection() }}"
id="comment{{$comment->local_id}}"
@@ -37,6 +38,12 @@
@if(userCan('comment-create-all'))
@endif
+ @if(userCan('comment-update', $comment) || userCan('comment-delete', $comment))
+
+ @endif
@if(userCan('comment-update', $comment))
@endif
diff --git a/routes/web.php b/routes/web.php
index 818472583..ea3efe1ac 100644
--- a/routes/web.php
+++ b/routes/web.php
@@ -179,6 +179,8 @@ Route::middleware('auth')->group(function () {
// Comments
Route::post('/comment/{pageId}', [ActivityControllers\CommentController::class, 'savePageComment']);
+ Route::put('/comment/{id}/archive', [ActivityControllers\CommentController::class, 'archive']);
+ Route::put('/comment/{id}/unarchive', [ActivityControllers\CommentController::class, 'unarchive']);
Route::put('/comment/{id}', [ActivityControllers\CommentController::class, 'update']);
Route::delete('/comment/{id}', [ActivityControllers\CommentController::class, 'destroy']);
diff --git a/tests/Entity/CommentTest.php b/tests/Entity/CommentTest.php
index 973b2b81d..baf0d392b 100644
--- a/tests/Entity/CommentTest.php
+++ b/tests/Entity/CommentTest.php
@@ -106,6 +106,66 @@ class CommentTest extends TestCase
$this->assertActivityExists(ActivityType::COMMENT_DELETE);
}
+ public function test_comment_archive_and_unarchive()
+ {
+ $this->asAdmin();
+ $page = $this->entities->page();
+
+ $comment = Comment::factory()->make();
+ $page->comments()->save($comment);
+ $comment->refresh();
+
+ $this->put("/comment/$comment->id/archive");
+
+ $this->assertDatabaseHas('comments', [
+ 'id' => $comment->id,
+ 'archived' => true,
+ ]);
+
+ $this->assertActivityExists(ActivityType::COMMENT_UPDATE);
+
+ $this->put("/comment/$comment->id/unarchive");
+
+ $this->assertDatabaseHas('comments', [
+ 'id' => $comment->id,
+ 'archived' => false,
+ ]);
+
+ $this->assertActivityExists(ActivityType::COMMENT_UPDATE);
+ }
+
+ public function test_archive_endpoints_require_delete_or_edit_permissions()
+ {
+ $viewer = $this->users->viewer();
+ $page = $this->entities->page();
+
+ $comment = Comment::factory()->make();
+ $page->comments()->save($comment);
+ $comment->refresh();
+
+ $endpoints = ["/comment/$comment->id/archive", "/comment/$comment->id/unarchive"];
+
+ foreach ($endpoints as $endpoint) {
+ $resp = $this->actingAs($viewer)->put($endpoint);
+ $this->assertPermissionError($resp);
+ }
+
+ $this->permissions->grantUserRolePermissions($viewer, ['comment-delete-all']);
+
+ foreach ($endpoints as $endpoint) {
+ $resp = $this->actingAs($viewer)->put($endpoint);
+ $resp->assertOk();
+ }
+
+ $this->permissions->removeUserRolePermissions($viewer, ['comment-delete-all']);
+ $this->permissions->grantUserRolePermissions($viewer, ['comment-update-all']);
+
+ foreach ($endpoints as $endpoint) {
+ $resp = $this->actingAs($viewer)->put($endpoint);
+ $resp->assertOk();
+ }
+ }
+
public function test_scripts_cannot_be_injected_via_comment_html()
{
$page = $this->entities->page();