Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions app/Enums/CommentStateEnum.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php

declare(strict_types=1);

namespace App\Enums;

enum CommentStateEnum: string
{
case Viewing = 'viewing';
case Editing = 'editing';
case Flagging = 'flagging';
case Replying = 'replying';
case Moderating = 'moderating';
}
3 changes: 3 additions & 0 deletions app/Enums/LivewireEventEnum.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,12 @@
enum LivewireEventEnum: string
{
case CommentDeleted = 'comment-deleted';
case CommentFlagCancelled = 'comment-flag-cancelled';
case CommentFlagDeleted = 'comment-flag-deleted';
case CommentFlagged = 'comment-flagged';
case CommentStored = 'comment-stored';
case CommentUpdated = 'comment-updated';
case CommentFormStateChanged = 'comment-form-state-changed';
case EditorUpdated = 'editor-updated';
case EscapeKeyClicked = 'escape-key-clicked';
case FavoriteDeleted = 'favorite-deleted';
Expand All @@ -19,6 +21,7 @@ enum LivewireEventEnum: string
case FlagDeleted = 'flag-deleted';
case HideFlagCommentForm = 'hide-flag-comment-form';
case PostDeleted = 'post-deleted';
case PostFlagCancelled = 'post-flag-cancelled';
case PostFlagDeleted = 'post-flag-deleted';
case PostFlagged = 'post-flagged';
case PostStored = 'post-stored';
Expand Down
27 changes: 27 additions & 0 deletions app/Enums/ModerationTypeEnum.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php

declare(strict_types=1);

namespace App\Enums;

enum ModerationTypeEnum: string
{
case Blur = 'blur';
case Comment = 'comment';
case Edit = 'edit';
case Remove = 'remove';
case Replace = 'replace';
case Wrap = 'wrap';

public function label(): string
{
return match ($this) {
self::Blur => 'Blur',
self::Comment => 'Comment',
self::Edit => 'Edit',
self::Remove => 'Remove',
self::Replace => 'Replace',
self::Wrap => 'Wrap',
};
}
}
31 changes: 31 additions & 0 deletions app/Livewire/Comments/CommentBlur.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php

declare(strict_types=1);

namespace App\Livewire\Comments;

use App\Enums\ModerationTypeEnum;
use App\Traits\CommentComponentTrait;
use App\Traits\CommentComponentStateTrait;
use Illuminate\Contracts\View\View;
use Livewire\Component;

final class CommentBlur extends Component
{
use CommentComponentStateTrait;
use CommentComponentTrait;

// State
public bool $isBlurred = true;

public function render(): View
{
$moderatorComment = $this->moderatorCommentsByType?->get(ModerationTypeEnum::Blur->value);

return view('livewire.comments.comment-blur', [
'comment' => $this->comment,
'childComments' => $this->childComments,
'blurMessage' => $moderatorComment?->body ?? '',
]);
}
}
154 changes: 78 additions & 76 deletions app/Livewire/Comments/CommentComponent.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,121 +4,123 @@

namespace App\Livewire\Comments;

use App\Enums\CommentStateEnum;
use App\Enums\LivewireEventEnum;
use App\Enums\ModerationTypeEnum;
use App\Models\Comment;
use App\Models\Flag;
use App\Models\Post;
use App\Models\User;
use App\Traits\CommentComponentTrait;
use Illuminate\Contracts\View\View;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Collection;
use Livewire\Attributes\Computed;
use Livewire\Attributes\On;
use Livewire\Component;

final class CommentComponent extends Component
{
use CommentComponentTrait;

// Data
public ?int $authorizedUserId;
public string $body = '';
public ?int $parentId = null;
public int $flagCount = 0;
public string $flagIconFilename = 'flag';
public string $flagButtonText = '';
public bool $userFlagged = false;

// State
public bool $isEditing = false;
public bool $isFlagging = false;
public bool $isReplying = false;

public Comment $comment;
public CommentForm $commentForm;
public Post $post;
public ?User $user;
public CommentStateEnum $state = CommentStateEnum::Viewing;

public function mount(Comment $comment, Post $post): void
#[Computed]
public function isInitiallyBlurred(): bool
{
$this->authorizedUserId = auth()->id() ?? null;

$this->comment = $comment;
$this->commentForm->setComment($comment);

$this->post = $post;

$this->body = $comment->body;

$this->user = auth()->user() ?? null;

$this->updateFlagCount();
$this->hasUserFlagged();

$this->flagIconFilename = $this->getFlagIconFilename();
$this->flagButtonText = $this->getFlagTitleText();
}

public function render(): View
{
return view('livewire.comments.comment-component');
return $this->moderatorCommentsByType?->get(ModerationTypeEnum::Blur->value) !== null;
}

private function getFlagIconFilename(): string
public function mount(int $commentId, ?Comment $comment, ?Collection $childComments): void
{
return $this->userFlagged ? 'flag-fill' : 'flag';
// On mount we expect the comment list to provide the comment model
// and the moderator comments collection.
$this->commentId = $commentId;
$this->comment = $comment ?? Comment::find($commentId);
$this->childComments = $childComments;

// Decide whether to blur the component initially.
$this->isBlurred = $this->isInitiallyBlurred;
}

private function getFlagTitleText(): string
public function render(): View
{
return $this->userFlagged ? trans('Remove flag') : trans('Flag this comment');
// If the comment has been replaced, just render the moderation message.
// TODO: if it is possible to have a top-level comment be marked with a
// moderation type, we may want to render it via this view. But it may
// also just be about changing the border for a regular rendering.
$moderatorReplaceComment = $this->moderatorCommentsByType?->get(ModerationTypeEnum::Replace->value);
$moderatorWrapComment = $this->moderatorCommentsByType?->get(ModerationTypeEnum::Wrap->value);
$moderatorBlurComment = $this->moderatorCommentsByType?->get(ModerationTypeEnum::Blur->value);
$moderationType = null;

if ($moderatorReplaceComment !== null &&
($moderatorWrapComment === null || $moderatorReplaceComment->created_at > $moderatorWrapComment->created_at)) {
$moderationType = ModerationTypeEnum::Replace;
} elseif ($moderatorWrapComment !== null) {
$moderationType = ModerationTypeEnum::Wrap;
}

// If there are no decorations to apply, just render the basic comment component.
return view('livewire.comments.comment-component', [
'comment' => $this->comment,
'childComments' => $this->childComments,
'moderationType' => $moderationType,
'isInitiallyBlurred' => $this->isInitiallyBlurred,
'replacedByCommentId' => $moderatorReplaceComment?->id,
'wrappedByCommentId' => $moderatorWrapComment?->id,
'blurredByCommentId' => $moderatorBlurComment?->id,
'isEditing' => $this->state === CommentStateEnum::Editing,
'isFlagging' => $this->state === CommentStateEnum::Flagging,
'isReplying' => $this->state === CommentStateEnum::Replying,
'isModerating' => $this->state === CommentStateEnum::Moderating,
]);
}

private function hasUserFlagged(): void
#[On([
LivewireEventEnum::EscapeKeyClicked->value,
])]
public function closeForm(): void
{
$userFlagCount = DB::table(table: 'markable_flags')
->where(column: 'user_id', operator: '=', value: auth()->id())
->where(column: 'markable_id', operator: '=', value: $this->comment->id)
->where(column: 'markable_type', operator: 'LIKE', value: '%Comment%')
->count();

$this->userFlagged = $userFlagCount > 0;
$this->state = CommentStateEnum::Viewing;
}

#[On([
LivewireEventEnum::CommentStored->value,
LivewireEventEnum::CommentDeleted->value,
LivewireEventEnum::CommentFlagged->value,
LivewireEventEnum::CommentUpdated->value,
LivewireEventEnum::EscapeKeyClicked->value,
])]
public function closeForm(): void
public function reloadChildComments(int $id, ?int $parentId): void
{
$this->reset([
'body',
]);
if ($parentId === $this->commentId) {
$this->childComments = $this->commentRepository->getCommentsByParentId($parentId);
unset($this->moderatorCommentsByType, $this->isInitiallyBlurred);

$this->stopEditing();
$this->stopFlagging();
$this->stopReplying();
// Re-evaluate whether the comment should be blurred.
$this->isBlurred = $this->isInitiallyBlurred;
}
}

private function updateFlagCount(): void
public function setState(CommentStateEnum $state): void
{
$this->flagCount = Flag::count($this->comment);
$this->state = $state;
}

#[On('comment-flagged.{comment.id}')]
public function addUserFlag(): void
public function addUserFlag(int $id): void
{
\Log::debug('CommentComponent::addUserFlag');
$this->userFlagged = true;
$this->flagCount++;
if ($id !== $this->comment->id) {
return;
}

unset($this->userFlagged, $this->flagCount);

$this->state = CommentStateEnum::Viewing;
}

#[On('comment-flag-deleted.{comment.id}')]
public function removeUserFlag(): void
public function removeUserFlag(int $id): void
{
$this->userFlagged = false;
$this->flagCount--;
if ($id !== $this->comment->id) {
return;
}

unset($this->userFlagged, $this->flagCount);

$this->state = CommentStateEnum::Viewing;
}
}
35 changes: 35 additions & 0 deletions app/Livewire/Comments/CommentContent.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?php

declare(strict_types=1);

namespace App\Livewire\Comments;

use App\Enums\CommentStateEnum;
use App\Traits\CommentComponentTrait;
use App\Traits\CommentComponentStateTrait;
use Illuminate\Contracts\View\View;
use Livewire\Component;

final class CommentContent extends Component
{
use CommentComponentStateTrait;
use CommentComponentTrait;

public function render(): View
{
$data = [
'authorizedUserId' => auth()->id() ?? null,
'comment' => $this->comment,
'body' => $this->comment->body,
'flagCount' => $this->flagCount,
'userFlagged' => $this->userFlagged,
'isEditing' => $this->state === CommentStateEnum::Editing,
'isFlagging' => $this->state === CommentStateEnum::Flagging,
'isReplying' => $this->state === CommentStateEnum::Replying,
'isModerating' => $this->state === CommentStateEnum::Moderating,
];

// If there are no decorations to apply, just render the basic comment component.
return view('livewire.comments.comment-content', $data);
}
}
Loading