From 7667f36535bc3e8947653f83cefaad9c4f7b0c74 Mon Sep 17 00:00:00 2001 From: William Rose Date: Thu, 24 Jul 2025 23:39:35 -0700 Subject: [PATCH] Allow restoring comments --- app/Enums/ModerationTypeEnum.php | 2 + app/Livewire/Comments/CommentBlur.php | 5 +- app/Livewire/Comments/CommentComponent.php | 32 ++------- .../Comments/CommentFormComponent.php | 8 ++- app/Livewire/Comments/CommentRemoval.php | 27 ++++++++ app/Livewire/Comments/CommentReplacement.php | 5 +- app/Livewire/Comments/CommentWrapper.php | 5 +- app/Providers/AppServiceProvider.php | 5 ++ app/Traits/CommentComponentTrait.php | 54 ++++++++++++--- ..._reset_to_comment_moderation_type_enum.php | 23 +++++++ resources/sass/modules/_posts.scss | 12 ++++ resources/sass/themes/modules/_themeable.scss | 4 ++ .../comments/comment-component.blade.php | 68 ++++++++++--------- .../comments/comment-form-component.blade.php | 8 ++- .../comments/comment-removal.blade.php | 18 +++++ .../partials/comment-blurrable.blade.php | 12 ---- .../partials/comment-content.blade.php | 38 ----------- .../toggle-moderating-button.blade.php | 2 + 18 files changed, 194 insertions(+), 134 deletions(-) create mode 100644 app/Livewire/Comments/CommentRemoval.php create mode 100644 database/migrations/2025_07_25_023003_add_reset_to_comment_moderation_type_enum.php create mode 100644 resources/views/livewire/comments/comment-removal.blade.php delete mode 100644 resources/views/livewire/comments/partials/comment-blurrable.blade.php delete mode 100644 resources/views/livewire/comments/partials/comment-content.blade.php diff --git a/app/Enums/ModerationTypeEnum.php b/app/Enums/ModerationTypeEnum.php index a4a66cf2..dc4e8037 100644 --- a/app/Enums/ModerationTypeEnum.php +++ b/app/Enums/ModerationTypeEnum.php @@ -11,6 +11,7 @@ enum ModerationTypeEnum: string case Edit = 'edit'; case Remove = 'remove'; case Replace = 'replace'; + case Restore = 'restore'; case Wrap = 'wrap'; public function label(): string @@ -21,6 +22,7 @@ public function label(): string self::Edit => 'Edit', self::Remove => 'Remove', self::Replace => 'Replace', + self::Restore => 'Restore', self::Wrap => 'Wrap', }; } diff --git a/app/Livewire/Comments/CommentBlur.php b/app/Livewire/Comments/CommentBlur.php index 0ecaffd7..b351941d 100644 --- a/app/Livewire/Comments/CommentBlur.php +++ b/app/Livewire/Comments/CommentBlur.php @@ -4,7 +4,6 @@ namespace App\Livewire\Comments; -use App\Enums\ModerationTypeEnum; use App\Traits\CommentComponentTrait; use App\Traits\CommentComponentStateTrait; use Illuminate\Contracts\View\View; @@ -20,12 +19,10 @@ final class CommentBlur extends Component 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 ?? '', + 'blurMessage' => $this->blurComment?->body ?? '', ]); } } diff --git a/app/Livewire/Comments/CommentComponent.php b/app/Livewire/Comments/CommentComponent.php index 3f78da63..008a6546 100644 --- a/app/Livewire/Comments/CommentComponent.php +++ b/app/Livewire/Comments/CommentComponent.php @@ -7,11 +7,11 @@ use App\Enums\CommentStateEnum; use App\Enums\LivewireEventEnum; use App\Enums\ModerationTypeEnum; +use App\Enums\RoleNameEnum; use App\Models\Comment; use App\Traits\CommentComponentTrait; use Illuminate\Contracts\View\View; use Illuminate\Support\Collection; -use Livewire\Attributes\Computed; use Livewire\Attributes\On; use Livewire\Component; @@ -22,12 +22,6 @@ final class CommentComponent extends Component // State public CommentStateEnum $state = CommentStateEnum::Viewing; - #[Computed] - public function isInitiallyBlurred(): bool - { - return $this->moderatorCommentsByType?->get(ModerationTypeEnum::Blur->value) !== null; - } - public function mount(int $commentId, ?Comment $comment, ?Collection $childComments): void { // On mount we expect the comment list to provide the comment model @@ -42,21 +36,7 @@ public function mount(int $commentId, ?Comment $comment, ?Collection $childComme public function render(): View { - // 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; - } + $moderationType = $this->appearanceComment?->moderation_type ?? null; // If there are no decorations to apply, just render the basic comment component. return view('livewire.comments.comment-component', [ @@ -64,9 +44,9 @@ public function render(): View 'childComments' => $this->childComments, 'moderationType' => $moderationType, 'isInitiallyBlurred' => $this->isInitiallyBlurred, - 'replacedByCommentId' => $moderatorReplaceComment?->id, - 'wrappedByCommentId' => $moderatorWrapComment?->id, - 'blurredByCommentId' => $moderatorBlurComment?->id, + 'isRemoved' => $moderationType === ModerationTypeEnum::Remove && !auth()->user()?->hasRole(RoleNameEnum::MODERATOR->value), + 'appearanceCommentId' => $this->appearanceComment?->id, + 'blurCommentId' => $this->blurComment?->id, 'isEditing' => $this->state === CommentStateEnum::Editing, 'isFlagging' => $this->state === CommentStateEnum::Flagging, 'isReplying' => $this->state === CommentStateEnum::Replying, @@ -90,7 +70,7 @@ public function reloadChildComments(int $id, ?int $parentId): void { if ($parentId === $this->commentId) { $this->childComments = $this->commentRepository->getCommentsByParentId($parentId); - unset($this->moderatorCommentsByType, $this->isInitiallyBlurred); + unset($this->appearanceComment, $this->blurComment, $this->isInitiallyBlurred); // Re-evaluate whether the comment should be blurred. $this->isBlurred = $this->isInitiallyBlurred; diff --git a/app/Livewire/Comments/CommentFormComponent.php b/app/Livewire/Comments/CommentFormComponent.php index f8b315ca..acf18230 100644 --- a/app/Livewire/Comments/CommentFormComponent.php +++ b/app/Livewire/Comments/CommentFormComponent.php @@ -119,11 +119,13 @@ public function render(): View if ($this->isModerating) { $data['bodyLabel'] = trans('Original comment'); $data['buttonText'] = trans(match ($this->moderationType) { + ModerationTypeEnum::Comment => 'Comment as moderator', ModerationTypeEnum::Edit => 'Edit comment', - ModerationTypeEnum::Remove => 'Remove comment', - ModerationTypeEnum::Replace => 'Replace comment', - ModerationTypeEnum::Wrap => 'Wrap comment', ModerationTypeEnum::Blur => 'Blur comment', + ModerationTypeEnum::Wrap => 'Wrap comment', + ModerationTypeEnum::Replace => 'Replace comment', + ModerationTypeEnum::Remove => 'Remove comment', + ModerationTypeEnum::Restore => 'Restore comment', default => 'Moderate', }); } elseif ($this->isReplying) { diff --git a/app/Livewire/Comments/CommentRemoval.php b/app/Livewire/Comments/CommentRemoval.php new file mode 100644 index 00000000..c55c388d --- /dev/null +++ b/app/Livewire/Comments/CommentRemoval.php @@ -0,0 +1,27 @@ + $this->comment, + 'childComments' => $this->childComments, + 'moderatorComment' => $this->appearanceComment, + 'isModerating' => $this->state === CommentStateEnum::Moderating, + ]); + } +} diff --git a/app/Livewire/Comments/CommentReplacement.php b/app/Livewire/Comments/CommentReplacement.php index c8f4c3d5..472857a7 100644 --- a/app/Livewire/Comments/CommentReplacement.php +++ b/app/Livewire/Comments/CommentReplacement.php @@ -5,7 +5,6 @@ namespace App\Livewire\Comments; use App\Enums\CommentStateEnum; -use App\Enums\ModerationTypeEnum; use App\Traits\CommentComponentTrait; use App\Traits\CommentComponentStateTrait; use Illuminate\Contracts\View\View; @@ -18,12 +17,10 @@ final class CommentReplacement extends Component public function render(): View { - $moderatorComment = $this->moderatorCommentsByType?->get(ModerationTypeEnum::Replace->value); - return view('livewire.comments.comment-replacement', [ 'comment' => $this->comment, 'childComments' => $this->childComments, - 'moderatorComment' => $moderatorComment, + 'moderatorComment' => $this->appearanceComment, 'isModerating' => $this->state === CommentStateEnum::Moderating, ]); } diff --git a/app/Livewire/Comments/CommentWrapper.php b/app/Livewire/Comments/CommentWrapper.php index 3f372fb7..37ead2e8 100644 --- a/app/Livewire/Comments/CommentWrapper.php +++ b/app/Livewire/Comments/CommentWrapper.php @@ -4,7 +4,6 @@ namespace App\Livewire\Comments; -use App\Enums\ModerationTypeEnum; use App\Traits\CommentComponentTrait; use App\Traits\CommentComponentStateTrait; use Illuminate\Contracts\View\View; @@ -20,12 +19,10 @@ final class CommentWrapper extends Component public function render(): View { - $moderatorComment = $this->moderatorCommentsByType?->get(ModerationTypeEnum::Wrap->value); - return view('livewire.comments.comment-wrapper', [ 'comment' => $this->comment, 'childComments' => $this->childComments, - 'moderatorComment' => $moderatorComment, + 'moderatorComment' => $this->appearanceComment, 'isInitiallyBlurred' => $this->isInitiallyBlurred, ]); } diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 85c9f88d..80055ebb 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -4,11 +4,13 @@ namespace App\Providers; +use App\Enums\RoleNameEnum; use App\Traits\LoggingTrait; use App\Traits\SubsiteTrait; use App\Traits\UrlTrait; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\Relation; +use Illuminate\Support\Facades\Blade; use Illuminate\Support\ServiceProvider; use Psr\Container\ContainerExceptionInterface; use Psr\Container\NotFoundExceptionInterface; @@ -30,6 +32,9 @@ public function boot(): void } catch (NotFoundExceptionInterface|ContainerExceptionInterface $exception) { $this->logError($exception); } + Blade::if('moderator', function () { + return auth()?->user()?->hasRole(RoleNameEnum::MODERATOR->value); + }); Model::shouldBeStrict(); diff --git a/app/Traits/CommentComponentTrait.php b/app/Traits/CommentComponentTrait.php index f8f50f1f..dd6610ab 100644 --- a/app/Traits/CommentComponentTrait.php +++ b/app/Traits/CommentComponentTrait.php @@ -36,20 +36,58 @@ public function userFlagged(): bool return $this->comment?->userFlagged() ?? false; } + /** + * Finds the most recent moderator remove, replace, or wrap comment. + * + * The effect of Reset is to override any previous appearance-modifying comment + * and restore the default appearance for the original comment. + */ #[Computed] - public function moderatorCommentsByType(): ?Collection - { - return $this->childComments?->filter( - fn($comment) => - $comment->moderation_type !== null && - $comment->moderation_type->value !== ModerationTypeEnum::Comment->value, - )->keyBy(fn($comment) => $comment->moderation_type->value ?? 'none'); + public function appearanceComment(): ?Comment + { + $appearanceComment = $this->childComments?->last( + fn($comment) => $comment->moderation_type !== null && match ($comment->moderation_type) { + ModerationTypeEnum::Remove, ModerationTypeEnum::Replace, ModerationTypeEnum::Wrap, ModerationTypeEnum::Restore => true, + default => false, + }, + ); + + // If the last appearance-modifying comment is a Restore, return null. + if ($appearanceComment && $appearanceComment->moderation_type === ModerationTypeEnum::Restore) { + return null; + } + + return $appearanceComment; + } + + /** + * Finds the most recent moderator blur comment. + * + * Blurring of comments can coexist with wrapping, but not with removal or replacement. + * It is also reset by a later Restore comment. + */ + #[Computed] + public function blurComment(): ?Comment + { + $blurComment = $this->childComments?->last( + fn($comment) => $comment->moderation_type !== null && match ($comment->moderation_type) { + ModerationTypeEnum::Blur, ModerationTypeEnum::Remove, ModerationTypeEnum::Replace, ModerationTypeEnum::Restore => true, + default => false, + }, + ); + + // If the last blur-modifying comment is not a Blur, return null. + if ($blurComment && $blurComment->moderation_type !== ModerationTypeEnum::Blur) { + return null; + } + + return $blurComment; } #[Computed] public function isInitiallyBlurred(): bool { - return $this->moderatorCommentsByType?->get(ModerationTypeEnum::Blur->value) !== null; + return $this->blurComment !== null; } protected CommentRepositoryInterface $commentRepository; diff --git a/database/migrations/2025_07_25_023003_add_reset_to_comment_moderation_type_enum.php b/database/migrations/2025_07_25_023003_add_reset_to_comment_moderation_type_enum.php new file mode 100644 index 00000000..a9a59324 --- /dev/null +++ b/database/migrations/2025_07_25_023003_add_reset_to_comment_moderation_type_enum.php @@ -0,0 +1,23 @@ +enum('moderation_type', ['blur', 'comment', 'edit', 'remove', 'replace', 'restore', 'wrap'])->nullable()->change(); + }); + } + + public function down(): void + { + Schema::table('comments', function (Blueprint $table) { + $table->enum('moderation_type', ['blur', 'comment', 'edit', 'remove', 'replace', 'wrap'])->nullable()->change(); + }); + } +}; diff --git a/resources/sass/modules/_posts.scss b/resources/sass/modules/_posts.scss index 6c8be33a..9902dc48 100644 --- a/resources/sass/modules/_posts.scss +++ b/resources/sass/modules/_posts.scss @@ -17,6 +17,11 @@ border-radius: 0.25rem; } +.comment.moderator-removed { + display: none; +} + +.comment>.moderator-removed, .comment>.moderator-replaced, .comment>.moderator-hidden>summary, .comment .comment-container { @@ -48,9 +53,16 @@ .moderator-message { border: 1px solid light-dark(var(--base-color), var(--yellow-green)); +} + +.moderator-message:not(.moderator-removed) .moderator-content { color: light-dark(var(--base-color), var(--yellow-green)); } +.moderator-message.moderator-removed { + border: 1px solid var(--is-danger); +} + details.moderator-hidden { padding: 0; border-radius: inherit; diff --git a/resources/sass/themes/modules/_themeable.scss b/resources/sass/themes/modules/_themeable.scss index b684e677..aaa183ea 100644 --- a/resources/sass/themes/modules/_themeable.scss +++ b/resources/sass/themes/modules/_themeable.scss @@ -24,6 +24,10 @@ body { background-color: light-dark(var(--very-light-gray), var(--dark-base-color)); } +.comment:has(>.moderator-removed) { + background-color: inherit; +} + .main-contents>.post { background-color: inherit; } diff --git a/resources/views/livewire/comments/comment-component.blade.php b/resources/views/livewire/comments/comment-component.blade.php index 7cdc1ad2..1fc22d41 100644 --- a/resources/views/livewire/comments/comment-component.blade.php +++ b/resources/views/livewire/comments/comment-component.blade.php @@ -2,13 +2,26 @@ use App\Enums\ModerationTypeEnum; @endphp -
- @if ($moderationType === ModerationTypeEnum::Replace) + @if ($moderationType === ModerationTypeEnum::Remove) + @moderator + + @endmoderator + @elseif ($moderationType === ModerationTypeEnum::Replace) @elseif ($moderationType === ModerationTypeEnum::Wrap) - @elseif ($isInitiallyBlurred) - @else - + @if ($isInitiallyBlurred) + + @else + + @endif @endif @if ($isEditing === true) @@ -84,14 +99,3 @@ /> @endif
- -@script - -@endscript \ No newline at end of file diff --git a/resources/views/livewire/comments/comment-form-component.blade.php b/resources/views/livewire/comments/comment-form-component.blade.php index b2aafd31..78f6ab0b 100644 --- a/resources/views/livewire/comments/comment-form-component.blade.php +++ b/resources/views/livewire/comments/comment-form-component.blade.php @@ -24,11 +24,13 @@ id="moderation-type-{{ $idSuffix }}" class="form-select"> + - - - + + + + @endif diff --git a/resources/views/livewire/comments/comment-removal.blade.php b/resources/views/livewire/comments/comment-removal.blade.php new file mode 100644 index 00000000..eb74915c --- /dev/null +++ b/resources/views/livewire/comments/comment-removal.blade.php @@ -0,0 +1,18 @@ +
+
+ {!! $moderatorComment->body !!} +
+
+ + + @auth + @include('livewire.comments.partials.toggle-moderating-button') + @endauth +
+
\ No newline at end of file diff --git a/resources/views/livewire/comments/partials/comment-blurrable.blade.php b/resources/views/livewire/comments/partials/comment-blurrable.blade.php deleted file mode 100644 index 6831422e..00000000 --- a/resources/views/livewire/comments/partials/comment-blurrable.blade.php +++ /dev/null @@ -1,12 +0,0 @@ -
-
- @include('livewire.comments.partials.comment-content') -
-
- @if (!empty($blurMessage)) - {!! $blurMessage !!} - @endif -
-
diff --git a/resources/views/livewire/comments/partials/comment-content.blade.php b/resources/views/livewire/comments/partials/comment-content.blade.php deleted file mode 100644 index 611e4622..00000000 --- a/resources/views/livewire/comments/partials/comment-content.blade.php +++ /dev/null @@ -1,38 +0,0 @@ -
-
- {!! $body !!} -
- -
- - - @auth - - - - - @if ($comment->user_id === auth()->id()) - @include('livewire.comments.partials.toggle-editing-button') - @endif - - @include('livewire.comments.partials.toggle-replying-button') - - @include('livewire.comments.partials.toggle-moderating-button') - @endauth - - @include('livewire.comments.partials.toggle-flagging-button') -
-
diff --git a/resources/views/livewire/comments/partials/toggle-moderating-button.blade.php b/resources/views/livewire/comments/partials/toggle-moderating-button.blade.php index cde77dcd..f5057e30 100644 --- a/resources/views/livewire/comments/partials/toggle-moderating-button.blade.php +++ b/resources/views/livewire/comments/partials/toggle-moderating-button.blade.php @@ -1,3 +1,4 @@ +@moderator +@endmoderator