Add Livewire comment system

This commit is contained in:
2026-01-10 15:02:14 +01:00
parent 67f5d0db8b
commit 5f575024e2
13 changed files with 456 additions and 33 deletions

90
app/Livewire/Comment.php Normal file
View File

@@ -0,0 +1,90 @@
<?php
namespace App\Livewire;
use Livewire\Component;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
class Comment extends Component
{
use AuthorizesRequests;
public $comment;
public $isReplying = false;
public $replyState = [
'body' => ''
];
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');
}
}

56
app/Livewire/Comments.php Normal file
View File

@@ -0,0 +1,56 @@
<?php
namespace App\Livewire;
use Livewire\Component;
use Livewire\WithPagination;
class Comments extends Component
{
use WithPagination;
public $model;
public $newCommentState = [
'body' => ''
];
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
]);
}
}

61
app/Models/Comment.php Normal file
View File

@@ -0,0 +1,61 @@
<?php
namespace App\Models;
use App\Models\Presenters\CommentPresenter;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Factories\HasFactory;
class Comment extends Model
{
use HasFactory, SoftDeletes;
/**
* The attributes that are mass assignable.
*
* @var string[]
*/
protected $fillable = [
'body'
];
public function presenter()
{
return new CommentPresenter($this);
}
public function user()
{
return $this->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;
}
}

View File

@@ -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'];

View File

@@ -0,0 +1,28 @@
<?php
namespace App\Models\Presenters;
use App\Models\Comment;
use Illuminate\Support\Str;
class CommentPresenter
{
public $comment;
public function __construct(Comment $comment)
{
$this->comment = $comment;
}
public function markdownBody()
{
return Str::of($this->comment->body)->markdown([
'html_input' => 'strip',
]);
}
public function relativeCreatedAt()
{
return $this->comment->created_at->diffForHumans();
}
}

View File

@@ -0,0 +1,22 @@
<?php
namespace App\Policies;
use App\Models\User;
use App\Models\Comment;
use Illuminate\Auth\Access\HandlesAuthorization;
class CommentPolicy
{
use HandlesAuthorization;
public function update(User $user, Comment $comment): bool
{
return $user->id === $comment->user_id;
}
public function destroy(User $user, Comment $comment): bool
{
return $user->id === $comment->user_id;
}
}