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
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@
"awcodes/filament-tiptap-editor": "^3.5",
"filament/filament": "^3",
"kalnoy/nestedset": "^6.0",
"spatie/laravel-package-tools": "^1.15.0"
"spatie/laravel-package-tools": "^1.15.0",
"tangodev-it/filament-emoji-picker": "^1.0"
},
"require-dev": {
"barryvdh/laravel-ide-helper": "^3.5",
Expand Down
34 changes: 22 additions & 12 deletions config/nested-comments.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,24 +21,34 @@
'reaction' => null,
],
'allowed-reactions' => [
'👍', // thumbs up
'👎', // thumbs down
'❤️', // heart
'😂', // laughing
'😮', // surprised
'😢', // crying
'😡', // angry
'🔥', // fire
'🎉', // party popper
'🚀', // rocket
'👍' => 'thumbs up', // thumbs up
'👎' => 'thumbs down', // thumbs down
'❤️' => 'heart', // heart
'😂' => 'laughing', // laughing
'😮' => 'surprised', // surprised
'😢' => 'crying', // crying
'💯' => 'hundred points', // angry
'🔥' => 'fire', // fire
'🎉' => 'party popper', // party popper
'🚀' => 'rocket', // rocket
],
'allow-all-reactions' => env('ALLOW_ALL_REACTIONS', false), // Allow any other emoji apart from the ones listed above
'allow-multiple-reactions' => 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),
'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),
'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',
],
];
2 changes: 1 addition & 1 deletion resources/dist/nested-comments.css

Large diffs are not rendered by default.

7 changes: 4 additions & 3 deletions resources/views/livewire/comment-card.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
/>
<div x-on:mouseover="showFullDate = true" x-on:mouseout="showFullDate = false" class="cursor-pointer">
<p class="text-sm font-semibold text-gray-900 dark:text-white">
{{ $this->comment->user?->name }}
{{ $this->comment->commentator }}
</p>
<p x-show="!showFullDate"
class="text-xs text-gray-500 dark:text-gray-400">
Expand All @@ -26,7 +26,7 @@ class="text-xs text-gray-500 dark:text-gray-400"
<div class="prose my-4 max-w-none dark:prose-invert">
{!! e(new \Illuminate\Support\HtmlString($this->comment?->body)) !!}
</div>
<div class="flex flex-wrap items-center space-x-2">
<div class="flex flex-wrap items-center md:space-x-4 gap-2">
<x-filament::link
size="xs"
class="cursor-pointer"
Expand All @@ -42,6 +42,7 @@ class="cursor-pointer"
</span>
@endif
</x-filament::link>
<livewire:nested-comments::reaction-panel :record="$this->comment"/>
</div>
</div>
@if($showReplies)
Expand All @@ -55,7 +56,7 @@ class="cursor-pointer"
:key="$comment->getKey()"
:commentable="$comment->commentable"
:reply-to="$comment"
:adding-comment="true"
:adding-comment="false"
wire:loading.attr="disabled"
/>
<x-filament::icon-button
Expand Down
46 changes: 46 additions & 0 deletions resources/views/livewire/reaction-panel.blade.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<div class="flex items-center gap-2 flex-wrap">
@foreach($this->record->reactions_map->filter(fn($reaction) => collect($reaction)->get('reactions') > 0) as $reaction => $attribs)
<x-filament::button
x-on:click="$wire.react('{{$reaction}}')"
@class([
'font-light' => true,
])
title="{{$reaction}} {{collect($attribs)->get('reactions')}} {{str(collect($attribs)->get('name'))->plural(collect($attribs)->get('reactions'))}}"
:outlined="true"
:color="collect($attribs)->get('meToo') ? 'primary' : 'gray'" size="xs"
>
<span class="text-md">{{$reaction}} {{\Illuminate\Support\Number::forHumans(collect($attribs)->get('reactions'), maxPrecision: 2)}}</span>
</x-filament::button>
@endforeach
<x-filament::dropdown placement="bottom-start">
<x-slot name="trigger">
<x-filament::button outlined color="gray" badge="+" size="xs" title="Add reaction">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5"
stroke="currentColor" class="size-4">
<path stroke-linecap="round" stroke-linejoin="round"
d="M15.182 15.182a4.5 4.5 0 0 1-6.364 0M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0ZM9.75 9.75c0 .414-.168.75-.375.75S9 10.164 9 9.75 9.168 9 9.375 9s.375.336.375.75Zm-.375 0h.008v.015h-.008V9.75Zm5.625 0c0 .414-.168.75-.375.75s-.375-.336-.375-.75.168-.75.375-.75.375.336.375.75Zm-.375 0h.008v.015h-.008V9.75Z" />
</svg>
</x-filament::button>
</x-slot>
<div class="p-4 flex items-center flex-wrap gap-2">
@foreach($this->record->reactions_map as $reaction => $attribs)
<x-filament::button
x-on:click="$wire.react('{{$reaction}}')"
@class([
'font-light' => true,
])
:outlined="true" :color="collect($attribs)->get('meToo') ? 'primary' : 'gray'" size="md" title="{{collect($attribs)->get('name')}}"
>
<span class="text-lg">{{$reaction}}</span>
</x-filament::button>
@endforeach
</div>
</x-filament::dropdown>
</div>
@script
<script>
function addReaction(reaction) {
console.log('About to add a reaction:', reaction);
}
</script>
@endscript
92 changes: 83 additions & 9 deletions src/Concerns/HasReactions.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,14 @@

use Coolsam\NestedComments\Models\Reaction;
use Coolsam\NestedComments\NestedComments;
use Exception;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\MorphMany;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Auth;
use Throwable;

use function config;

trait HasReactions
{
Expand All @@ -15,13 +20,64 @@ public function reactions(): MorphMany
return $this->morphMany(config('nested-comments.models.reaction'), 'reactable');
}

public function getReactionsCountAttribute(): int
public function getTotalReactionsAttribute(): int
{
return $this->reactions()->count();
}

public function getReactionsCountsAttribute()
{
return $this->reactions()->get(['id', 'emoji'])->groupBy('emoji')->map(function ($item) {
return count($item);
});
}

public function getEmojiReactors()
{
return $this->reactions()->get(['id', 'emoji', 'user_id', 'guest_id'])->groupBy('emoji')->map(function ($item) {
return $item->map(function ($reaction) {
return [
'id' => $reaction->getKey(),
'user_id' => $reaction->getAttribute('user_id'),
'guest_id' => $reaction->getAttribute('guest_id'),
'name' => $reaction->getAttribute('user_id') ? call_user_func(config('nested-comments.closures.getUserNameUsing'), $reaction->getAttribute('user')) : $reaction->getAttribute('guest_name'),
];
});
});
}

public function getMyReactionsAttribute(): array
{
$allowGuest = config('nested-comments.allow-guest-reactions', false);

if ($allowGuest && ! Auth::check()) {
$guestId = app(NestedComments::class)->getGuestId();
if (! $guestId) {
return [];
}

return $this->reactions()
->where('guest_id', '=', $guestId)
->pluck('emoji')->values()->toArray();
}

return $this->reactions()
->where('user_id', '=', Auth::id())
->pluck('emoji')->values()->toArray();
}

public function iHaveReacted(string $emoji): bool
{
$myReactions = $this->getMyReactionsAttribute();
if (empty($myReactions)) {
return false;
}

return in_array($emoji, $myReactions);
}

/**
* @throws \Throwable
* @throws Throwable
*/
public function react(string $emoji): Reaction | Model | int
{
Expand All @@ -33,7 +89,7 @@ public function react(string $emoji): Reaction | Model | int
return $id;
}
if (! $this->isAllowed($emoji)) {
throw new \Exception('This reaction is not allowed.');
throw new Exception('This reaction is not allowed.');
}

return $this->reactions()->create([
Expand All @@ -46,21 +102,21 @@ public function react(string $emoji): Reaction | Model | int
}

/**
* @throws \Exception
* @throws Exception
*/
protected function getExistingReaction(string $emoji): Reaction | Model | null
{
$allowMultiple = \config('nested-comments.allow-multiple-reactions', false);
$allowGuest = \config('nested-comments.allow-guest-reactions', false);
$allowMultiple = config('nested-comments.allow-multiple-reactions', false);
$allowGuest = config('nested-comments.allow-guest-reactions', false);

if (! $allowGuest && ! Auth::check()) {
throw new \Exception('You must be logged in to react.');
throw new Exception('You must be logged in to react.');
}

if ($allowGuest && ! Auth::check()) {
$guestId = app(NestedComments::class)->getGuestId();
if (! $guestId) {
throw new \Exception('Sorry, your guest session has not bee setup.');
throw new Exception('Sorry, your guest session has not bee setup.');
}
$existingQuery = $this->reactions()
->where('guest_id', '=', $guestId);
Expand All @@ -79,11 +135,29 @@ protected function getExistingReaction(string $emoji): Reaction | Model | null

public function isAllowed(string $emoji): bool
{
$allowOthers = config('nested-comments.allow-all-reactions', false);
$allowed = config('nested-comments.allowed-reactions', []);
if (empty($allowed)) {
return true;
}

return in_array($emoji, $allowed);
return $allowOthers || collect($allowed)->has($emoji);
}

public function getReactionsMapAttribute(): Collection
{
$reactions = collect($this->getReactionsCountsAttribute());
$myReactions = collect($this->getMyReactionsAttribute())->mapWithKeys(fn ($value) => [$value => $value]);

return collect(config('nested-comments.allowed-reactions', []))->mapWithKeys(function ($value, $emoji) use ($reactions, $myReactions) {
$name = $value;

return [$emoji => [
'name' => $name,
'emoji' => $emoji,
'reactions' => $reactions->get($emoji),
'meToo' => $myReactions->has($emoji),
]];
});
}
}
2 changes: 2 additions & 0 deletions src/Livewire/AddComment.php
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ public function form(Form $form): Form
->label(__('Your comment'))
->profile('minimal')
->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()))
->required()
->autofocus(),
Expand Down
38 changes: 38 additions & 0 deletions src/Livewire/ReactionPanel.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?php

namespace Coolsam\NestedComments\Livewire;

use Illuminate\Database\Eloquent\Model;
use Livewire\Component;

class ReactionPanel extends Component
{
protected $listeners = [
'refresh' => '$refresh',
];

public array $allReactions = [];

public Model $record;

public function mount(mixed $record = null): void
{
if (! $record?->getKey()) {
throw new \Error('The Reactable $record property is required.');
}
$this->record = $record;
}

public function render()
{
return view('nested-comments::livewire.reaction-panel');
}

public function react($emoji): void
{
if (method_exists($this->record, 'react')) {
$this->record->react($emoji);
$this->dispatch('refresh')->self();
}
}
}
7 changes: 5 additions & 2 deletions src/NestedComments.php
Original file line number Diff line number Diff line change
Expand Up @@ -99,10 +99,13 @@ public function getUserMentions(string $query)
})->toArray();
}

public function getCurrentThreadUsers(string $searchQuery, Model $commentable)
public function getCurrentThreadUsers(string $searchQuery, $commentable): mixed
{
$userModel = config('nested-comments.models.user', config('auth.providers.users.model', 'App\\Models\\User'));
$ids = $commentable->comments()->pluck('user_id')->filter()->unique()->toArray();
$ids = [];
if (method_exists($commentable, 'comments')) {
$ids = $commentable->comments()->pluck('user_id')->filter()->unique()->toArray();
}

return $userModel::query()
->whereIn('id', $ids)
Expand Down
2 changes: 2 additions & 0 deletions src/NestedCommentsServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use Coolsam\NestedComments\Livewire\AddComment;
use Coolsam\NestedComments\Livewire\CommentCard;
use Coolsam\NestedComments\Livewire\Comments;
use Coolsam\NestedComments\Livewire\ReactionPanel;
use Coolsam\NestedComments\Testing\TestsNestedComments;
use Filament\Support\Assets\AlpineComponent;
use Filament\Support\Assets\Asset;
Expand Down Expand Up @@ -223,6 +224,7 @@ protected function getLivewireComponents(): array
'comments' => Comments::class,
'comment-card' => CommentCard::class,
'add-comment' => AddComment::class,
'reaction-panel' => ReactionPanel::class,
];
}
}