diff --git a/app/Livewire/Comment.php b/app/Livewire/Comment.php new file mode 100644 index 0000000..65d823f --- /dev/null +++ b/app/Livewire/Comment.php @@ -0,0 +1,90 @@ + '' + ]; + + public $isEditing = false; + + public $editState = [ + 'body' => '' + ]; + + protected $listeners = [ + 'refresh' => '$refresh' + ]; + + protected $validationAttributes = [ + 'replyState.body' => 'reply' + ]; + + public function updatedIsEditing($isEditing) + { + if (! $isEditing) { + return; + } + + $this->editState = [ + 'body' => $this->comment->body + ]; + } + + public function editComment() + { + $this->authorize('update', $this->comment); + + $this->comment->update($this->editState); + + $this->isEditing = false; + } + + public function deleteComment() + { + $this->authorize('destroy', $this->comment); + + $this->comment->delete(); + } + + public function postReply() + { + if (! $this->comment->depth() < 2) { + return; + } + + $this->validate([ + 'replyState.body' => 'required' + ]); + + $reply = $this->comment->children()->make($this->replyState); + $reply->user()->associate(auth()->user()); + $reply->commentable()->associate($this->comment->commentable); + + $reply->save(); + + $this->replyState = [ + 'body' => '' + ]; + + $this->isReplying = false; + + $this->dispatch('refresh')->self(); + } + + public function render() + { + return view('livewire.comment'); + } +} \ No newline at end of file diff --git a/app/Livewire/Comments.php b/app/Livewire/Comments.php new file mode 100644 index 0000000..824a4ec --- /dev/null +++ b/app/Livewire/Comments.php @@ -0,0 +1,56 @@ + '' + ]; + + protected $validationAttributes = [ + 'newCommentState.body' => 'comment' + ]; + + protected $listeners = [ + 'refresh' => '$refresh' + ]; + + public function postComment() + { + $this->validate([ + 'newCommentState.body' => 'required' + ]); + + $comment = $this->model->comments()->make($this->newCommentState); + $comment->user()->associate(auth()->user()); + $comment->save(); + + $this->newCommentState = [ + 'body' => '' + ]; + + $this->resetPage(); + } + + public function render() + { + $comments = $this->model + ->comments() + ->with('user', 'children.user', 'children.children') + ->parent() + ->latest() + ->paginate(3); + + return view('livewire.comments', [ + 'comments' => $comments + ]); + } +} \ No newline at end of file diff --git a/app/Models/Comment.php b/app/Models/Comment.php new file mode 100644 index 0000000..45b4071 --- /dev/null +++ b/app/Models/Comment.php @@ -0,0 +1,61 @@ +belongsTo(User::class); + } + + public function scopeParent(Builder $builder) + { + $builder->whereNull('parent_id'); + } + + public function children() + { + return $this->hasMany(Comment::class, 'parent_id')->oldest(); + } + + public function commentable() + { + return $this->morphTo(); + } + + public function parent() + { + return $this->hasOne(Comment::class, 'id', 'parent_id'); + } + + // Recursevly calculates how deep the nesting is + public function depth(): int + { + return $this->parent + ? $this->parent->depth() + 1 + : 0; + } +} diff --git a/app/Models/Episode.php b/app/Models/Episode.php index 7029eb3..31ef83d 100644 --- a/app/Models/Episode.php +++ b/app/Models/Episode.php @@ -160,6 +160,11 @@ class Episode extends Model implements Sitemapable return cache()->remember('episodeComments' . $this->id, 300, fn() => $this->comments->count()); } + public function comments() + { + return $this->morphMany(Comment::class, 'commentable'); + } + public function getProblematicTags(): string { $problematicTags = ['Gore', 'Scat', 'Horror']; diff --git a/app/Models/Presenters/CommentPresenter.php b/app/Models/Presenters/CommentPresenter.php new file mode 100644 index 0000000..645f85f --- /dev/null +++ b/app/Models/Presenters/CommentPresenter.php @@ -0,0 +1,28 @@ +comment = $comment; + } + + public function markdownBody() + { + return Str::of($this->comment->body)->markdown([ + 'html_input' => 'strip', + ]); + } + + public function relativeCreatedAt() + { + return $this->comment->created_at->diffForHumans(); + } +} \ No newline at end of file diff --git a/app/Policies/CommentPolicy.php b/app/Policies/CommentPolicy.php new file mode 100644 index 0000000..4f52686 --- /dev/null +++ b/app/Policies/CommentPolicy.php @@ -0,0 +1,22 @@ +id === $comment->user_id; + } + + public function destroy(User $user, Comment $comment): bool + { + return $user->id === $comment->user_id; + } +} \ No newline at end of file diff --git a/database/migrations/2026_01_10_120521_create_comments_table.php b/database/migrations/2026_01_10_120521_create_comments_table.php new file mode 100644 index 0000000..98806de --- /dev/null +++ b/database/migrations/2026_01_10_120521_create_comments_table.php @@ -0,0 +1,38 @@ +id(); + $table->foreignId('user_id')->constrained()->cascadeOnDelete(); + $table->foreignId('parent_id')->nullable()->constrained('comments')->cascadeOnDelete(); + $table->morphs('commentable'); // What is being commented on + $table->text('body'); + $table->softDeletes(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('comments'); + + // Revert to old table from laravelista/comments + Schema::rename('comments_old', 'comments'); + } +}; diff --git a/resources/views/home/partials/comments.blade.php b/resources/views/home/partials/comments.blade.php index 5ed6af4..2548d70 100644 --- a/resources/views/home/partials/comments.blade.php +++ b/resources/views/home/partials/comments.blade.php @@ -31,7 +31,7 @@
{!! $comment->presenter()->markdownBody() !!}
+ @endif +No comments yet.
+ @endif +Log in to comment.
+ @endguest +- {{ __('home.latest-comments') }} -
- {{--@comments(['model' => $episode])--}} -
{{ $user->name }} - {{ \Carbon\Carbon::parse($comment->created_at)->diffForHumans() }}
- @else -{{ $user->name }} - {{ \Carbon\Carbon::parse($comment->created_at)->diffForHumans() }}
- @endif --