Comments: Added archive endpoints, messages, Js actions and tests
This commit is contained in:
parent
e8f44186a8
commit
8bdf948743
|
@ -53,6 +53,33 @@ class CommentRepo
|
||||||
return $comment;
|
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.
|
* Delete a comment from the system.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -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.
|
* Delete a comment from the system.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -30,6 +30,8 @@ return [
|
||||||
'create' => 'Create',
|
'create' => 'Create',
|
||||||
'update' => 'Update',
|
'update' => 'Update',
|
||||||
'edit' => 'Edit',
|
'edit' => 'Edit',
|
||||||
|
'archive' => 'Archive',
|
||||||
|
'unarchive' => 'Un-Archive',
|
||||||
'sort' => 'Sort',
|
'sort' => 'Sort',
|
||||||
'move' => 'Move',
|
'move' => 'Move',
|
||||||
'copy' => 'Copy',
|
'copy' => 'Copy',
|
||||||
|
|
|
@ -402,6 +402,8 @@ return [
|
||||||
'comment_deleted_success' => 'Comment deleted',
|
'comment_deleted_success' => 'Comment deleted',
|
||||||
'comment_created_success' => 'Comment added',
|
'comment_created_success' => 'Comment added',
|
||||||
'comment_updated_success' => 'Comment updated',
|
'comment_updated_success' => 'Comment updated',
|
||||||
|
'comment_archive_success' => 'Comment archived',
|
||||||
|
'comment_unarchive_success' => 'Comment un-archived',
|
||||||
'comment_view' => 'View comment',
|
'comment_view' => 'View comment',
|
||||||
'comment_jump_to_thread' => 'Jump to thread',
|
'comment_jump_to_thread' => 'Jump to thread',
|
||||||
'comment_delete_confirm' => 'Are you sure you want to delete this comment?',
|
'comment_delete_confirm' => 'Are you sure you want to delete this comment?',
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960"><path d="m480-240 160-160-56-56-64 64v-168h-80v168l-64-64-56 56 160 160ZM200-640v440h560v-440H200Zm0 520q-33 0-56.5-23.5T120-200v-499q0-14 4.5-27t13.5-24l50-61q11-14 27.5-21.5T250-840h460q18 0 34.5 7.5T772-811l50 61q9 11 13.5 24t4.5 27v499q0 33-23.5 56.5T760-120H200Zm16-600h528l-34-40H250l-34 40Zm264 300Z"/></svg>
|
After Width: | Height: | Size: 380 B |
|
@ -8,6 +8,7 @@ export class PageComment extends Component {
|
||||||
protected commentLocalId: string;
|
protected commentLocalId: string;
|
||||||
protected deletedText: string;
|
protected deletedText: string;
|
||||||
protected updatedText: string;
|
protected updatedText: string;
|
||||||
|
protected archiveText: string;
|
||||||
|
|
||||||
protected wysiwygEditor: any = null;
|
protected wysiwygEditor: any = null;
|
||||||
protected wysiwygLanguage: string;
|
protected wysiwygLanguage: string;
|
||||||
|
@ -20,6 +21,7 @@ export class PageComment extends Component {
|
||||||
protected editButton: HTMLElement;
|
protected editButton: HTMLElement;
|
||||||
protected deleteButton: HTMLElement;
|
protected deleteButton: HTMLElement;
|
||||||
protected replyButton: HTMLElement;
|
protected replyButton: HTMLElement;
|
||||||
|
protected archiveButton: HTMLElement;
|
||||||
protected input: HTMLInputElement;
|
protected input: HTMLInputElement;
|
||||||
|
|
||||||
setup() {
|
setup() {
|
||||||
|
@ -27,7 +29,8 @@ export class PageComment extends Component {
|
||||||
this.commentId = this.$opts.commentId;
|
this.commentId = this.$opts.commentId;
|
||||||
this.commentLocalId = this.$opts.commentLocalId;
|
this.commentLocalId = this.$opts.commentLocalId;
|
||||||
this.deletedText = this.$opts.deletedText;
|
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
|
// Editor reference and text options
|
||||||
this.wysiwygLanguage = this.$opts.wysiwygLanguage;
|
this.wysiwygLanguage = this.$opts.wysiwygLanguage;
|
||||||
|
@ -41,6 +44,7 @@ export class PageComment extends Component {
|
||||||
this.editButton = this.$refs.editButton;
|
this.editButton = this.$refs.editButton;
|
||||||
this.deleteButton = this.$refs.deleteButton;
|
this.deleteButton = this.$refs.deleteButton;
|
||||||
this.replyButton = this.$refs.replyButton;
|
this.replyButton = this.$refs.replyButton;
|
||||||
|
this.archiveButton = this.$refs.archiveButton;
|
||||||
this.input = this.$refs.input as HTMLInputElement;
|
this.input = this.$refs.input as HTMLInputElement;
|
||||||
|
|
||||||
this.setupListeners();
|
this.setupListeners();
|
||||||
|
@ -63,6 +67,10 @@ export class PageComment extends Component {
|
||||||
if (this.deleteButton) {
|
if (this.deleteButton) {
|
||||||
this.deleteButton.addEventListener('click', this.delete.bind(this));
|
this.deleteButton.addEventListener('click', this.delete.bind(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.archiveButton) {
|
||||||
|
this.archiveButton.addEventListener('click', this.archive.bind(this));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected toggleEditMode(show: boolean) : void {
|
protected toggleEditMode(show: boolean) : void {
|
||||||
|
@ -126,6 +134,15 @@ export class PageComment extends Component {
|
||||||
window.$events.success(this.deletedText);
|
window.$events.success(this.deletedText);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected async archive(): Promise<void> {
|
||||||
|
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 {
|
protected showLoading(): HTMLElement {
|
||||||
const loading = getLoading();
|
const loading = getLoading();
|
||||||
loading.classList.add('px-l');
|
loading.classList.add('px-l');
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
option:page-comment:comment-local-id="{{ $comment->local_id }}"
|
option:page-comment:comment-local-id="{{ $comment->local_id }}"
|
||||||
option:page-comment:updated-text="{{ trans('entities.comment_updated_success') }}"
|
option:page-comment:updated-text="{{ trans('entities.comment_updated_success') }}"
|
||||||
option:page-comment:deleted-text="{{ trans('entities.comment_deleted_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-language="{{ $locale->htmlLang() }}"
|
||||||
option:page-comment:wysiwyg-text-direction="{{ $locale->htmlDirection() }}"
|
option:page-comment:wysiwyg-text-direction="{{ $locale->htmlDirection() }}"
|
||||||
id="comment{{$comment->local_id}}"
|
id="comment{{$comment->local_id}}"
|
||||||
|
@ -37,6 +38,12 @@
|
||||||
@if(userCan('comment-create-all'))
|
@if(userCan('comment-create-all'))
|
||||||
<button refs="page-comment@reply-button" type="button" class="text-button text-muted hover-underline text-small p-xs">@icon('reply') {{ trans('common.reply') }}</button>
|
<button refs="page-comment@reply-button" type="button" class="text-button text-muted hover-underline text-small p-xs">@icon('reply') {{ trans('common.reply') }}</button>
|
||||||
@endif
|
@endif
|
||||||
|
@if(userCan('comment-update', $comment) || userCan('comment-delete', $comment))
|
||||||
|
<button refs="page-comment@archive-button"
|
||||||
|
type="button"
|
||||||
|
data-is-archived="{{ $comment->archived ? 'true' : 'false' }}"
|
||||||
|
class="text-button text-muted hover-underline text-small p-xs">@icon('archive') {{ trans('common.' . ($comment->archived ? 'unarchive' : 'archive')) }}</button>
|
||||||
|
@endif
|
||||||
@if(userCan('comment-update', $comment))
|
@if(userCan('comment-update', $comment))
|
||||||
<button refs="page-comment@edit-button" type="button" class="text-button text-muted hover-underline text-small p-xs">@icon('edit') {{ trans('common.edit') }}</button>
|
<button refs="page-comment@edit-button" type="button" class="text-button text-muted hover-underline text-small p-xs">@icon('edit') {{ trans('common.edit') }}</button>
|
||||||
@endif
|
@endif
|
||||||
|
|
|
@ -179,6 +179,8 @@ Route::middleware('auth')->group(function () {
|
||||||
|
|
||||||
// Comments
|
// Comments
|
||||||
Route::post('/comment/{pageId}', [ActivityControllers\CommentController::class, 'savePageComment']);
|
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::put('/comment/{id}', [ActivityControllers\CommentController::class, 'update']);
|
||||||
Route::delete('/comment/{id}', [ActivityControllers\CommentController::class, 'destroy']);
|
Route::delete('/comment/{id}', [ActivityControllers\CommentController::class, 'destroy']);
|
||||||
|
|
||||||
|
|
|
@ -106,6 +106,66 @@ class CommentTest extends TestCase
|
||||||
$this->assertActivityExists(ActivityType::COMMENT_DELETE);
|
$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()
|
public function test_scripts_cannot_be_injected_via_comment_html()
|
||||||
{
|
{
|
||||||
$page = $this->entities->page();
|
$page = $this->entities->page();
|
||||||
|
|
Loading…
Reference in New Issue