diff --git a/config/nested-comments.php b/config/nested-comments.php index dc1e777..bff56fa 100644 --- a/config/nested-comments.php +++ b/config/nested-comments.php @@ -1,6 +1,5 @@ env('ALLOW_MULTIPLE_REACTIONS', false), // Allow multiple reactions from the same user 'allow-guest-reactions' => env('ALLOW_GUEST_REACTIONS', false), // Allow guest users to react 'allow-guest-comments' => env('ALLOW_GUEST_COMMENTS', false), // Allow guest users to comment - 'closures' => [ - 'getUserNameUsing' => fn (Authenticatable | Model $user) => $user->getAttribute('name'), - 'getUserAvatarUsing' => fn ( - Authenticatable | Model | string $user - ) => app(\Coolsam\NestedComments\NestedComments::class)->geDefaultUserAvatar($user), - // 'getMentionsUsing' => fn (string $query) => app(\Coolsam\NestedComments\NestedComments::class)->getUserMentions($query), // Get mentions of all users in the DB - 'getMentionsUsing' => fn ( - string $query, - Model $commentable - ) => app(\Coolsam\NestedComments\NestedComments::class)->getCurrentThreadUsers($query, $commentable), - ], 'mentions' => [ 'items-placeholder' => 'Search users by name or email address', 'empty-items-message' => 'No users found', diff --git a/resources/views/components/comment-card.blade.php b/resources/views/components/comment-card.blade.php new file mode 100644 index 0000000..141be1d --- /dev/null +++ b/resources/views/components/comment-card.blade.php @@ -0,0 +1,13 @@ +@props([ + 'comment' => null, +]) +@if ($comment?->getKey()) + + @else +

+ {{ __('No comment provided.') }} +

+@endif \ No newline at end of file diff --git a/resources/views/filament/infolists/comment-card-entry.blade.php b/resources/views/filament/infolists/comment-card-entry.blade.php new file mode 100644 index 0000000..4106ea2 --- /dev/null +++ b/resources/views/filament/infolists/comment-card-entry.blade.php @@ -0,0 +1,8 @@ + + + diff --git a/resources/views/livewire/comment-card.blade.php b/resources/views/livewire/comment-card.blade.php index e72f699..f06cdd4 100644 --- a/resources/views/livewire/comment-card.blade.php +++ b/resources/views/livewire/comment-card.blade.php @@ -4,14 +4,14 @@

- {{ $this->comment->commentator }} + {{ $this->getCommentator() }}

diff --git a/resources/views/livewire/comments.blade.php b/resources/views/livewire/comments.blade.php index 8444736..09abd1f 100644 --- a/resources/views/livewire/comments.blade.php +++ b/resources/views/livewire/comments.blade.php @@ -17,7 +17,8 @@ @foreach($this->comments as $comment) + :comment="$comment" + /> @endforeach diff --git a/src/Concerns/HasComments.php b/src/Concerns/HasComments.php index 707dc3b..2233269 100644 --- a/src/Concerns/HasComments.php +++ b/src/Concerns/HasComments.php @@ -5,10 +5,15 @@ use Coolsam\NestedComments\Models\Comment; use Coolsam\NestedComments\NestedComments; use Exception; +use FilamentTiptapEditor\Data\MentionItem; +use Illuminate\Contracts\Auth\Authenticatable; +use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\MorphMany; use Illuminate\Support\Collection; +use function auth; + /** * @mixin Model */ @@ -97,7 +102,7 @@ public function editComment(Comment $comment, string $body, ?string $name = null app(NestedComments::class)->setGuestName($name); } - if (\auth()->check() && $comment->getAttribute('user_id') !== auth()->id()) { + if (auth()->check() && $comment->getAttribute('user_id') !== auth()->id()) { throw new Exception('You are not authorized to edit this comment.'); } @@ -123,7 +128,7 @@ public function deleteComment(Comment $comment): ?bool throw new Exception('You must be logged in to edit your comment.'); } - if (\auth()->check() && $comment->getAttribute('user_id') !== auth()->id()) { + if (auth()->check() && $comment->getAttribute('user_id') !== auth()->id()) { throw new Exception('You are not authorized to edit this comment.'); } @@ -136,4 +141,50 @@ public function deleteComment(Comment $comment): ?bool return $comment->delete(); } + + final public function getUserNameUsing(Comment $comment): string + { + return $this->getUserName($comment->getAttribute('user')); + } + + final public function getUserAvatarUsing(Comment $comment): ?string + { + $user = $comment->user ?? $comment->guest_name ?? 'Guest'; + + return $this->getUserAvatar($user); + } + + public function getUserName(Model | Authenticatable | null $user): string + { + return app(NestedComments::class)->getUserName($user); + } + + public function getUserAvatar(Model | Authenticatable | string | null $user): ?string + { + return app(NestedComments::class)->getDefaultUserAvatar($user); + } + + /** + * @return array + */ + public function getMentionsUsing(string $query): array + { + return $this->getMentionsQuery($query) +// ->where('username', 'like', "%{$query}%") + ->take(50) + ->get() + ->map(function ($user) { + return new MentionItem( + id: $user->getKey(), + label: $this->getUserName($user), + image: $this->getUserAvatar($user), + roundedImage: true, + ); + })->toArray(); + } + + public function getMentionsQuery(string $query): Builder + { + return app(NestedComments::class)->getUserMentionsQuery($query); + } } diff --git a/src/Filament/Infolists/CommentCardEntry.php b/src/Filament/Infolists/CommentCardEntry.php new file mode 100644 index 0000000..1d8d6bf --- /dev/null +++ b/src/Filament/Infolists/CommentCardEntry.php @@ -0,0 +1,59 @@ + 'full']; + + public static function make(?string $name = null): static + { + $name = $name ?? 'body'; + + return parent::make($name); // TODO: Change the autogenerated stub + } + + public function getUserNameUsing(Closure | string | null $callback = null): static + { + $this->userNameUsingClosure = $callback; + + return $this; + } + + public function getUserAvatarUsing(Closure | string | null $callback = null): static + { + $this->userAvatarUsingClosure = $callback; + + return $this; + } + + public function evaluateUserAvatar(): ?string + { + return $this->evaluate($this->userAvatarUsingClosure + ?? fn (Comment | Model $record) => app(NestedComments::class) + ->getDefaultUserAvatar($record->getAttribute('user') + ?? $record->getAttribute('guest_name') ?? 'Guest')); + } + + public function evaluateUserName(): ?string + { + return $this->evaluate($this->userNameUsingClosure + ?? fn (Comment | Model | null $record) => $record?->getAttribute('user')?->getAttribute('name') + ?? $record->getAttribute('guest_name')); + } +} diff --git a/src/Livewire/AddComment.php b/src/Livewire/AddComment.php index 1a4fce2..6a4ebce 100644 --- a/src/Livewire/AddComment.php +++ b/src/Livewire/AddComment.php @@ -2,9 +2,11 @@ namespace Coolsam\NestedComments\Livewire; +use Coolsam\NestedComments\Concerns\HasComments; use Coolsam\NestedComments\Models\Comment; -use Coolsam\NestedComments\NestedComments; use Coolsam\NestedComments\NestedCommentsServiceProvider; +use Error; +use Exception; use Filament\Forms\Concerns\InteractsWithForms; use Filament\Forms\Contracts\HasForms; use Filament\Forms\Form; @@ -30,10 +32,10 @@ class AddComment extends Component implements HasForms public ?Comment $replyTo = null; - public function mount(?Model $commentable = null, ?Comment $replyTo = null): void + public function mount(?Model $commentable, ?Comment $replyTo): void { if (! $commentable) { - throw new \Error('The $commentable property is required.'); + throw new Error('The $commentable property is required.'); } $this->commentable = $commentable; $this->replyTo = $replyTo; @@ -43,7 +45,7 @@ public function mount(?Model $commentable = null, ?Comment $replyTo = null): voi public function getCommentable(): Model { if (! $this->commentable) { - throw new \Error('The $commentable property is required.'); + throw new Error('The $commentable property is required.'); } return $this->commentable; @@ -51,7 +53,12 @@ public function getCommentable(): Model public function form(Form $form): Form { - $mentionsClosure = config('nested-comments.closures.getMentionsUsing', fn (string $query, Model $commentable) => app(NestedComments::class)->getUserMentions($query)); + /** + * @var Model|HasComments $commentable + * + * @phpstan-ignore-next-line + */ + $commentable = $this->getCommentable(); return $form ->schema([ @@ -61,7 +68,11 @@ public function form(Form $form): Form ->extraInputAttributes(['style' => 'min-height: 12rem;']) ->mentionItemsPlaceholder(config('nested-comments.mentions.items-placeholder', __('Search users by name or email address'))) ->emptyMentionItemsMessage(config('nested-comments.mentions.empty-items-message', __('No users found'))) - ->getMentionItemsUsing(fn (string $query) => $mentionsClosure($query, $this->getCommentable())) + /** + * @phpstan-ignore-next-line + */ + ->getMentionItemsUsing(fn (string $query) => $commentable->getMentionsUsing($query)) + ->maxContentWidth('full') ->required() ->autofocus(), ]) @@ -70,12 +81,17 @@ public function form(Form $form): Form } /** - * @throws \Exception + * @throws Exception */ public function create(): void { $data = $this->form->getState(); + /** + * @var Model|HasComments $commentable + * + * @phpstan-ignore-next-line + */ $commentable = $this->getCommentable(); /** diff --git a/src/Livewire/CommentCard.php b/src/Livewire/CommentCard.php index d566b6e..873a6f5 100644 --- a/src/Livewire/CommentCard.php +++ b/src/Livewire/CommentCard.php @@ -4,14 +4,22 @@ use Coolsam\NestedComments\Models\Comment; use Coolsam\NestedComments\NestedCommentsServiceProvider; +use Error; +use Filament\Support\Concerns\EvaluatesClosures; use Livewire\Component; class CommentCard extends Component { + use EvaluatesClosures; + public ?Comment $comment = null; public bool $showReplies = false; + public ?string $userAvatar = null; + + public ?string $userName = null; + protected $listeners = [ 'refresh' => 'refreshReplies', ]; @@ -19,7 +27,7 @@ class CommentCard extends Component public function mount(?Comment $comment = null): void { if (! $comment) { - throw new \Error('The $comment property is required.'); + throw new Error('The $comment property is required.'); } $this->comment = $comment; @@ -48,6 +56,21 @@ public function getAvatar() return ''; } - return call_user_func(config('nested-comments.closures.getUserAvatarUsing'), $this->comment->commentator); + /** + * @phpstan-ignore-next-line + */ + return $this->comment->commentable?->getUserAvatarUsing($this->comment); + } + + public function getCommentator(): string + { + if (! $this->comment) { + return ''; + } + + /** + * @phpstan-ignore-next-line + */ + return $this->comment->commentable?->getUserNameUsing($this->comment); } } diff --git a/src/Livewire/Comments.php b/src/Livewire/Comments.php index dfbbe05..e67dd33 100644 --- a/src/Livewire/Comments.php +++ b/src/Livewire/Comments.php @@ -29,6 +29,7 @@ class Comments extends Component public function mount(): void { $this->comments = collect(); + if (! $this->record) { throw new \Error('Record model (Commentable) is required'); } diff --git a/src/Models/Comment.php b/src/Models/Comment.php index 339e20e..b163d44 100644 --- a/src/Models/Comment.php +++ b/src/Models/Comment.php @@ -27,10 +27,6 @@ public function user(): BelongsTo public function getCommentatorAttribute() { - if ($this->user) { - return call_user_func(config('nested-comments.closures.getUserNameUsing'), $this->user); - } - return $this->getAttribute('guest_name'); } diff --git a/src/NestedComments.php b/src/NestedComments.php index 14df329..cac52eb 100644 --- a/src/NestedComments.php +++ b/src/NestedComments.php @@ -25,13 +25,15 @@ public function getGuestId(): ?string return session(self::GUEST_ID_FIELD); } + public function getUserName(Authenticatable | Model | null $user): string + { + return $user?->getAttribute('name') ?? $user?->getAttribute('guest_name') ?? 'Guest'; + } + public function getGuestName(): string { if (Auth::check()) { - return call_user_func(config( - 'nested-comments.closures.getUserNameUsing', - fn (Authenticatable | Model $user) => $user->getAttribute('name') - ), Auth::user()); + return $this->getUserName(Auth::user()); } return session(self::GUEST_NAME_FIELD, 'Guest'); @@ -66,7 +68,7 @@ public function classHasTrait(object | string $classInstance, string $trait): bo return in_array($trait, class_uses_recursive($classInstance)); } - public function geDefaultUserAvatar(Authenticatable | Model | string $user) + public function getDefaultUserAvatar(Authenticatable | Model | string $user) { if (is_string($user)) { $name = str($user) @@ -83,25 +85,30 @@ public function geDefaultUserAvatar(Authenticatable | Model | string $user) } } - public function getUserMentions(string $query) + public function getUserMentions(string $query): array { - $userModel = config('nested-comments.models.user', config('auth.providers.users.model', 'App\\Models\\User')); - - return $userModel::query() - ->where('name', 'like', "%{$query}%") - ->orWhere('email', 'like', "%{$query}%") + return $this->getUserMentionsQuery($query) ->take(20) ->get() ->map(function ($user) { return new MentionItem( id: $user->getKey(), - label: call_user_func(config('nested-comments.closures.getUserNameUsing'), $user), - image: call_user_func(config('nested-comments.closures.getUserAvatarUsing'), $user), + label: $this->getUserName($user), + image: $this->getDefaultUserAvatar($user), roundedImage: true, ); })->toArray(); } + public function getUserMentionsQuery(string $query): \Illuminate\Database\Eloquent\Builder + { + $userModel = config('nested-comments.models.user', config('auth.providers.users.model', 'App\\Models\\User')); + + return $userModel::query() + ->where('name', 'like', "%{$query}%") + ->orWhere('email', 'like', "%{$query}%"); + } + public function getCurrentThreadUsers(string $searchQuery, $commentable): mixed { $userModel = config('nested-comments.models.user', config('auth.providers.users.model', 'App\\Models\\User')); @@ -122,8 +129,8 @@ public function getCurrentThreadUsers(string $searchQuery, $commentable): mixed ->map(function ($user) { return new MentionItem( id: $user->getKey(), - label: call_user_func(config('nested-comments.closures.getUserNameUsing'), $user), - image: call_user_func(config('nested-comments.closures.getUserAvatarUsing'), $user), + label: $this->getUserName($user), + image: $this->getDefaultUserAvatar($user), roundedImage: true, ); })->toArray();