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
8 changes: 7 additions & 1 deletion config/nested-comments.php
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
<?php

use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Database\Eloquent\Model;

return [
'tables' => [
'comments' => 'comments',
Expand All @@ -10,7 +13,7 @@
'models' => [
'comment' => \Coolsam\NestedComments\Models\Comment::class,
'reaction' => \Coolsam\NestedComments\Models\Reaction::class,
'user' => env( 'AUTH_MODEL', 'App\Models\User'), // The model that will be used to get the authenticated user
'user' => env('AUTH_MODEL', 'App\Models\User'), // The model that will be used to get the authenticated user

Check failure on line 16 in config/nested-comments.php

View workflow job for this annotation

GitHub Actions / phpstan

Called 'env' outside of the config directory which returns null when the config is cached, use 'config'.
],

'policies' => [
Expand All @@ -29,7 +32,10 @@
'🎉', // party popper
'🚀', // rocket
],
'allow-multiple-reactions' => env('ALLOW_MULTIPLE_REACTIONS', false), // Allow multiple reactions from the same user

Check failure on line 35 in config/nested-comments.php

View workflow job for this annotation

GitHub Actions / phpstan

Called 'env' outside of the config directory which returns null when the config is cached, use 'config'.
'allow-guest-reactions' => env('ALLOW_GUEST_REACTIONS', false), // Allow guest users to react

Check failure on line 36 in config/nested-comments.php

View workflow job for this annotation

GitHub Actions / phpstan

Called 'env' outside of the config directory which returns null when the config is cached, use 'config'.
'allow-guest-comments' => env('ALLOW_GUEST_COMMENTS', false), // Allow guest users to comment

Check failure on line 37 in config/nested-comments.php

View workflow job for this annotation

GitHub Actions / phpstan

Called 'env' outside of the config directory which returns null when the config is cached, use 'config'.
'closures' => [
'getUserNameUsing' => fn (Authenticatable | Model $user) => $user->getAttribute('name')
]
];
6 changes: 6 additions & 0 deletions database/migrations/create_nested_comments_table.php.stub
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,10 @@ return new class extends Migration
$table->foreignId('user_id')->nullable()->constrained($users)->cascadeOnDelete();
$table->text('body');
$table->morphs('commentable');
$table->ulid('guest_id')->nullable()->index();
$table->string('guest_name')->nullable();
$table->ipAddress()->nullable();
$table->boolean('is_published')->default(false);
$table->timestamps();
});

Expand All @@ -25,7 +28,10 @@ return new class extends Migration
$table->foreignId('user_id')->nullable()->constrained($users)->cascadeOnDelete();
$table->morphs('reactable');
$table->string('emoji');
$table->ulid('guest_id')->nullable()->index();
$table->string('guest_name')->nullable();
$table->ipAddress()->nullable();
$table->boolean('is_published')->default(false);
$table->timestamps();
});
}
Expand Down
2 changes: 1 addition & 1 deletion resources/dist/nested-comments.css

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions resources/views/components/emoji-selector.blade.php
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ALL EMOJIS HERE
26 changes: 26 additions & 0 deletions resources/views/livewire/add-comment.blade.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<div>
@if($this->addingComment)
<form wire:submit.prevent="create" wire:loading.attr="disabled" class="space-y-4">
{{ $this->form }}

<x-filament::button type="submit">
Submit
</x-filament::button>
<x-filament::button type="button" color="gray" wire:click="showForm(false)">
Cancel
</x-filament::button>
</form>
@else
<x-filament::input.wrapper
:inline-prefix="true"
prefix-icon="heroicon-o-chat-bubble-bottom-center-text">
<x-filament::input
placeholder="Add a comment..."
type="text"
wire:click.prevent.stop="showForm(true)"
:readonly="true"
/>
</x-filament::input.wrapper>
@endif
<x-filament-actions::modals />
</div>
6 changes: 6 additions & 0 deletions resources/views/livewire/comment-card.blade.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<div class="my-4 p-4 bg-primary-50 rounded-lg ring-gray-100 dark:bg-primary-950 dark:text-white">
{{ $comment->commentator }}
<div class="prose max-w-none">
{!! e(new \Illuminate\Support\HtmlString($this->comment?->body)) !!}
</div>
</div>
14 changes: 14 additions & 0 deletions resources/views/livewire/comments.blade.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<x-filament::section icon="heroicon-o-chat-bubble-bottom-center-text" class="!ring-0">
<x-slot name="heading">
{{ __('Comments') }}
</x-slot>
<x-slot name="headerEnd">
<x-filament::button wire:click.prevent="refreshComments()">Refresh</x-filament::button>
</x-slot>
<livewire:nested-comments::add-comment :commentable="$this->record" />
@foreach($this->comments as $comment)
<livewire:nested-comments::comment-card
:key="$comment->getKey()"
:comment="$comment" />
@endforeach
</x-filament::section>
80 changes: 66 additions & 14 deletions src/Concerns/HasComments.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,16 @@
namespace Coolsam\NestedComments\Concerns;

use Coolsam\NestedComments\Models\Comment;
use Coolsam\NestedComments\NestedComments;
use Exception;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\MorphMany;
use Illuminate\Support\Collection;

/**
* @mixin Model
*/
trait HasComments

Check failure on line 15 in src/Concerns/HasComments.php

View workflow job for this annotation

GitHub Actions / phpstan

Trait Coolsam\NestedComments\Concerns\HasComments is used zero times and is not analysed.
{
public function comments(): MorphMany
{
Expand All @@ -21,7 +24,7 @@
return $this->comments()->count();
}

public function getCommentsTree($offset = null, $limit = null, $columns = ['*'])
public function getCommentsTree($offset = null, $limit = null, $columns = ['*']): Collection
{
$query = $this->comments()
->getQuery()
Expand All @@ -33,24 +36,32 @@
$query->limit($limit);
}

$columns = ['id', 'parent_id', '_lft', '_rgt', ...$columns];
if (filled($columns) && $columns[0] !== '*') {
$columns = ['id', 'parent_id', '_lft', '_rgt', ...$columns];
}

return collect($query->get($columns)->map(function (Comment $comment) use ($columns) {
return $query->get($columns)->map(function (Comment $comment) use ($columns) {
$descendants = $comment->getDescendants($columns);
return collect($comment->toArray())->put('descendants', $descendants->toArray());
})->toArray());
$comment->setAttribute('replies', $descendants);

return $comment;
});
}

/**
* @throws \Exception
* @throws Exception
*/
public function addComment(string $comment, mixed $parentId = null)
public function comment(string $comment, mixed $parentId = null, ?string $name = null)
{
$allowGuest = config('nested-comments.allow-guest-comments', false);
if (! $allowGuest && ! auth()->check()) {
throw new \Exception('You must be logged in to comment.');
throw new Exception('You must be logged in to comment.');
}

if ($name) {
app(NestedComments::class)->setGuestName($name);
}
$guestId = app(NestedComments::class)->getGuestId();
$guestName = app(NestedComments::class)->getGuestName();
if ($allowGuest && ! auth()->check()) {
$userId = null;
} else {
Expand All @@ -63,21 +74,62 @@
'commentable_id' => $this->getKey(),
'commentable_type' => $this->getMorphClass(),
'parent_id' => $parentId,
'guest_id' => $guestId,
'guest_name' => $guestName,
'ip_address' => request()->ip(),
]);
}

/**
* @throws \Exception
* @throws Exception
*/
public function editComment(Comment $comment, string $body, ?string $name = null): ?bool
{
$allowGuest = config('nested-comments.allow-guest-comments', false);

if (! auth()->check() && ! $allowGuest) {
throw new Exception('You must be logged in to edit your comment.');
}

if ($name) {
app(NestedComments::class)->setGuestName($name);
}

if (\auth()->check() && $comment->getAttribute('user_id') !== auth()->id()) {
throw new Exception('You are not authorized to edit this comment.');
}

if ($allowGuest && ! auth()->check()) {
$guestId = app(NestedComments::class)->getGuestId();
if ($comment->getAttribute('guest_id') !== $guestId) {
throw new Exception('You are not authorized to edit this comment.');
}
}
$guestName = app(NestedComments::class)->getGuestName();

return $comment->update(['body' => $body, 'guest_name' => $guestName, 'ip_address' => request()->ip()]);
}

/**
* @throws Exception
*/
public function deleteComment(Comment $comment): ?bool
{
if (! auth()->check()) {
throw new \Exception('You must be logged in to delete your comment.');
$allowGuest = config('nested-comments.allow-guest-comments', false);

if (! auth()->check() && ! $allowGuest) {
throw new Exception('You must be logged in to edit your comment.');
}

if ($comment->getAttribute('user_id') !== auth()->id()) {
throw new \Exception('You are not authorized to delete this comment.');
if (\auth()->check() && $comment->getAttribute('user_id') !== auth()->id()) {
throw new Exception('You are not authorized to edit this comment.');
}

if ($allowGuest && ! auth()->check()) {
$guestId = app(NestedComments::class)->getGuestId();
if ($comment->getAttribute('guest_id') !== $guestId) {
throw new Exception('You are not authorized to edit this comment.');
}
}

return $comment->delete();
Expand Down
15 changes: 12 additions & 3 deletions src/Concerns/HasReactions.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace Coolsam\NestedComments\Concerns;

use Coolsam\NestedComments\Models\Reaction;
use Coolsam\NestedComments\NestedComments;
use Illuminate\Database\Eloquent\Relations\MorphMany;
use Illuminate\Support\Facades\Auth;

Expand All @@ -21,7 +22,7 @@ public function getReactionsCountAttribute(): int
/**
* @throws \Throwable
*/
public function toggleReaction(string $emoji): Reaction | int
public function react(string $emoji): Reaction | int
{
$existing = $this->getExistingReaction($emoji);
if ($existing) {
Expand All @@ -33,9 +34,12 @@ public function toggleReaction(string $emoji): Reaction | int
if (! $this->isAllowed($emoji)) {
throw new \Exception('This reaction is not allowed.');
}

return $this->reactions()->create([
'user_id' => Auth::check() ? Auth::id() : null,
'emoji' => $emoji,
'guest_id' => app(NestedComments::class)->getGuestId(),
'guest_name' => app(NestedComments::class)->getGuestName(),
'ip_address' => request()->ip(),
]);
}
Expand All @@ -53,14 +57,18 @@ protected function getExistingReaction(string $emoji): ?Reaction
}

if ($allowGuest && ! Auth::check()) {
$guestId = app(NestedComments::class)->getGuestId();
if (! $guestId) {
throw new \Exception('Sorry, your guest session has not bee setup.');
}
$existingQuery = $this->reactions()
->where('ip_address', '=', request()->ip());
->where('guest_id', '=', $guestId);

} else {
$existingQuery = $this->reactions()
->where('user_id', '=', Auth::id());

}

if ($allowMultiple) {
$existingQuery->where('emoji', '=', $emoji);
}
Expand All @@ -74,6 +82,7 @@ public function isAllowed(string $emoji): bool
if (empty($allowed)) {
return true;
}

return in_array($emoji, $allowed);
}
}
24 changes: 24 additions & 0 deletions src/Http/Middleware/GuestCommentatorMiddleware.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php

namespace Coolsam\NestedComments\Http\Middleware;

use Closure;
use Coolsam\NestedComments\NestedComments;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;

class GuestCommentatorMiddleware
{
/**
* Handle an incoming request.
*
* @param Closure(Request): (Response) $next
*/
public function handle(Request $request, Closure $next): Response
{
app(NestedComments::class)->setOrGetGuestId();
app(NestedComments::class)->setOrGetGuestName();

return $next($request);
}
}
Loading
Loading