Skip to content

Commit 0db7b5c

Browse files
committed
Enhance comment system with subscription management features
1 parent 86a3cdb commit 0db7b5c

19 files changed

+807
-31
lines changed

.idea/modules.xml

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

README.md

Lines changed: 120 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ use Kirschbaum\Commentions\Filament\Actions\CommentsAction;
103103
CommentsAction::make()
104104
->mentionables(User::all())
105105
])
106-
````
106+
```
107107

108108
3. Or as a header action:
109109

@@ -120,6 +120,100 @@ protected function getHeaderActions(): array
120120
}
121121
```
122122

123+
### Subscription Management
124+
125+
Commentions includes a subscription system that allows users to subscribe to receive notifications when new comments are added to a commentable resource.
126+
127+
#### Subscription Actions
128+
129+
You can add subscription actions to your Filament resources:
130+
131+
```php
132+
use Kirschbaum\Commentions\Filament\Actions\SubscriptionAction;
133+
134+
// In header actions
135+
protected function getHeaderActions(): array
136+
{
137+
return [
138+
SubscriptionAction::make(),
139+
];
140+
}
141+
142+
// In table actions (Filament 3)
143+
->actions([
144+
SubscriptionTableAction::make(),
145+
])
146+
147+
// In record actions (Filament 4)
148+
->recordActions([
149+
SubscriptionAction::make(),
150+
])
151+
```
152+
153+
#### Subscription Sidebar
154+
155+
When using comments in modals, a subscription sidebar is automatically displayed showing:
156+
- Subscribe/unsubscribe button for the current user
157+
- List of users currently subscribed to the commentable
158+
- Real-time updates when subscription status changes
159+
160+
##### Livewire options
161+
162+
When using the `commentions::comments` Livewire component directly, you can control the sidebar and its contents via component properties:
163+
164+
- `sidebarEnabled` (bool, default: true): toggles the entire subscription sidebar
165+
- `showSubscribers` (bool, default: `config('commentions.subscriptions.show_subscribers', true)`): toggles the subscribers list within the sidebar
166+
167+
Examples:
168+
169+
```php
170+
// Hide the sidebar entirely
171+
<livewire:commentions::comments :record="$record" :sidebar-enabled="false" />
172+
173+
// Keep the sidebar, but hide the subscribers list (uses config default if omitted)
174+
<livewire:commentions::comments :record="$record" :show-subscribers="false" />
175+
```
176+
177+
Inside the component/template you can also rely on these computed properties:
178+
179+
- `canSubscribe`: whether the current user can subscribe
180+
- `isSubscribed`: whether the current user is subscribed to the current record
181+
- `subscribers`: a collection of current subscribers
182+
183+
The component exposes a `toggleSubscription()` action which subscribes/unsubscribes the current user.
184+
185+
#### Disabling the Subscription Sidebar
186+
187+
You can disable the subscription sidebar if you don't want subscription functionality:
188+
189+
```php
190+
use Kirschbaum\Commentions\Filament\Actions\CommentsAction;
191+
192+
->recordActions([
193+
CommentsAction::make()
194+
->mentionables(User::all())
195+
->disableSidebar()
196+
])
197+
```
198+
199+
#### Subscription Methods
200+
201+
The `HasComments` trait provides methods for managing subscriptions programmatically:
202+
203+
```php
204+
// Subscribe a user
205+
$commentable->subscribe($user);
206+
207+
// Unsubscribe a user
208+
$commentable->unsubscribe($user);
209+
210+
// Check if a user is subscribed
211+
$isSubscribed = $commentable->isSubscribed($user);
212+
213+
// Get all subscribers
214+
$subscribers = $commentable->getSubscribers();
215+
```
216+
123217
***
124218

125219
### Configuration
@@ -329,6 +423,31 @@ Events are dispatched when a comment is created, reacted to, or when users are m
329423
- `Kirschbaum\Commentions\Events\CommentWasCreatedEvent`
330424
- `Kirschbaum\Commentions\Events\CommentWasReactedEvent`
331425

426+
#### Subscription Events
427+
428+
When a new comment is created, all subscribed users receive notifications through the `UserIsSubscribedToCommentableEvent`. You can listen to this event to send custom notifications:
429+
430+
```php
431+
namespace App\Listeners;
432+
433+
use Illuminate\Queue\InteractsWithQueue;
434+
use Illuminate\Contracts\Queue\ShouldQueue;
435+
use App\Notifications\NewCommentNotification;
436+
use Kirschbaum\Commentions\Events\UserIsSubscribedToCommentableEvent;
437+
438+
class SendSubscribedUserNotification implements ShouldQueue
439+
{
440+
use InteractsWithQueue;
441+
442+
public function handle(UserIsSubscribedToCommentableEvent $event): void
443+
{
444+
$event->user->notify(
445+
new NewCommentNotification($event->comment)
446+
);
447+
}
448+
}
449+
```
450+
332451
### Sending notifications when a user is mentioned
333452

334453
Every time a user is mentioned, the `Kirschbaum\Commentions\Events\UserWasMentionedEvent` is dispatched. You can listen to this event and send notifications to the mentioned user.

config/commentions.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,5 +50,11 @@
5050
// (UserWasMentionedEvent). When false, a distinct
5151
// UserIsSubscribedToCommentableEvent will be dispatched instead.
5252
'dispatch_as_mention' => false,
53+
// Controls whether the subscribers list is shown in the sidebar UI
54+
'show_subscribers' => true,
55+
// Automatically subscribe the author when they add a comment
56+
'auto_subscribe_on_comment' => true,
57+
// Automatically subscribe a user when they are mentioned in a comment
58+
'auto_subscribe_on_mention' => true,
5359
],
5460
];

resources/dist/commentions.css

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

resources/views/comments-modal.blade.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,7 @@
88
:per-page="$perPage ?? 5"
99
:load-more-label="$loadMoreLabel ?? 'Show more'"
1010
:per-page-increment="$perPageIncrement ?? null"
11+
:sidebar-enabled="$sidebarEnabled ?? true"
12+
:show-subscribers="$showSubscribers ?? true"
1113
/>
1214
</div>

resources/views/comments.blade.php

Lines changed: 30 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,18 @@
11
@use('\Kirschbaum\Commentions\Config')
22

3-
<div class="comm:space-y-2" x-data="{ wasFocused: false }">
4-
@if (Config::resolveAuthenticatedUser()?->can('create', Config::getCommentModel()))
5-
<form wire:submit.prevent="save" x-cloak>
6-
{{-- tiptap editor --}}
7-
<div class="comm:relative tip-tap-container comm:mb-2" x-on:click="wasFocused = true" wire:ignore>
8-
<div
9-
x-data="editor(@js($commentBody), @js($this->mentions), 'comments')"
10-
>
11-
<div x-ref="element"></div>
3+
<div class="comm:flex comm:gap-4 comm:h-full" x-data="{ wasFocused: false }">
4+
{{-- Main Comments Area --}}
5+
<div class="comm:flex-1 comm:space-y-2">
6+
@if (Config::resolveAuthenticatedUser()?->can('create', Config::getCommentModel()))
7+
<form wire:submit.prevent="save" x-cloak>
8+
{{-- tiptap editor --}}
9+
<div class="comm:relative tip-tap-container comm:mb-2" x-on:click="wasFocused = true" wire:ignore>
10+
<div
11+
x-data="editor(@js($commentBody), @js($this->mentions), 'comments')"
12+
>
13+
<div x-ref="element"></div>
14+
</div>
1215
</div>
13-
</div>
1416

1517
<template x-if="wasFocused">
1618
<div>
@@ -30,13 +32,22 @@
3032
</form>
3133
@endif
3234

33-
<livewire:commentions::comment-list
34-
:record="$record"
35-
:mentionables="$this->mentions"
36-
:polling-interval="$pollingInterval"
37-
:paginate="$paginate ?? true"
38-
:per-page="$perPage ?? 5"
39-
:load-more-label="$loadMoreLabel ?? 'Show more'"
40-
:per-page-increment="$perPageIncrement ?? null"
41-
/>
35+
<livewire:commentions::comment-list
36+
:record="$record"
37+
:mentionables="$this->mentions"
38+
:polling-interval="$pollingInterval"
39+
:paginate="$paginate ?? true"
40+
:per-page="$perPage ?? 5"
41+
:load-more-label="$loadMoreLabel ?? 'Show more'"
42+
:per-page-increment="$perPageIncrement ?? null"
43+
/>
44+
</div>
45+
46+
{{-- Subscription Sidebar --}}
47+
@if ($this->canSubscribe && $this->resolvedSidebarEnabled)
48+
<livewire:commentions::subscription-sidebar
49+
:record="$record"
50+
:show-subscribers="$this->resolvedShowSubscribers"
51+
/>
52+
@endif
4253
</div>
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
<div class="comm:w-48 comm:flex-shrink-0 comm:pl-2 comm:ml-2">
2+
<div class="comm:sticky comm:top-4">
3+
<div class="comm:bg-gray-50 comm:dark:bg-gray-800 comm:rounded-lg comm:p-4 comm:border comm:border-gray-200 comm:dark:border-gray-700">
4+
<div class="comm:flex comm:items-center comm:gap-2 comm:mb-3">
5+
<x-filament::icon
6+
icon="heroicon-o-bell"
7+
class="comm:w-4 comm:h-4 comm:text-gray-700 comm:dark:text-gray-300"
8+
/>
9+
<h3 class="comm:text-sm comm:font-bold comm:text-gray-900 comm:dark:text-gray-100">
10+
Notifications
11+
</h3>
12+
</div>
13+
14+
@if ($this->isSubscribed)
15+
<x-filament::button
16+
wire:click="toggleSubscription"
17+
wire:target="toggleSubscription"
18+
wire:loading.attr="disabled"
19+
color="gray"
20+
size="xs"
21+
class="comm:w-full comm:mb-2"
22+
>
23+
<x-filament::icon
24+
icon="heroicon-s-bell-slash"
25+
class="comm:w-3 comm:h-3 comm:mr-1"
26+
/>
27+
Unsubscribe
28+
</x-filament::button>
29+
@else
30+
<x-filament::button
31+
wire:click="toggleSubscription"
32+
wire:target="toggleSubscription"
33+
wire:loading.attr="disabled"
34+
color="gray"
35+
size="xs"
36+
class="comm:w-full comm:mb-2"
37+
>
38+
<x-filament::icon
39+
icon="heroicon-o-bell"
40+
class="comm:w-3 comm:h-3 comm:mr-1"
41+
/>
42+
Subscribe
43+
</x-filament::button>
44+
@endif
45+
46+
{{-- Subscribers List --}}
47+
@if ($showSubscribers && $this->subscribers->isNotEmpty())
48+
<div class="comm:border-t comm:border-gray-200 comm:dark:border-gray-600 comm:pt-3">
49+
<div class="comm:flex comm:items-center comm:gap-2 comm:mb-3">
50+
<x-filament::icon
51+
icon="heroicon-o-users"
52+
class="comm:w-4 comm:h-4 comm:text-gray-700 comm:dark:text-gray-300"
53+
/>
54+
<span class="comm:text-sm comm:font-bold comm:text-gray-900 comm:dark:text-gray-100">
55+
Subscribers ({{ $this->subscribers->count() }})
56+
</span>
57+
</div>
58+
<div class="comm:space-y-1">
59+
@foreach ($this->subscribers->take(5) as $subscriber)
60+
<div class="comm:flex comm:items-center comm:gap-2">
61+
@if ($subscriber instanceof \Filament\Models\Contracts\HasAvatar && $subscriber->getFilamentAvatarUrl())
62+
<img
63+
src="{{ $subscriber->getFilamentAvatarUrl() }}"
64+
alt="{{ $subscriber->name }}"
65+
class="comm:w-4 comm:h-4 comm:rounded-full comm:object-cover comm:flex-shrink-0"
66+
/>
67+
@else
68+
<div class="comm:w-4 comm:h-4 comm:rounded-full comm:bg-gray-300 comm:dark:bg-gray-600 comm:flex-shrink-0 comm:flex comm:items-center comm:justify-center">
69+
<span class="comm:text-xs comm:font-medium comm:text-gray-600 comm:dark:text-gray-300">
70+
{{ substr($subscriber->name, 0, 1) }}
71+
</span>
72+
</div>
73+
@endif
74+
<span class="comm:text-xs comm:text-gray-600 comm:dark:text-gray-400 comm:truncate">
75+
{{ $subscriber->name }}
76+
</span>
77+
</div>
78+
@endforeach
79+
@if ($this->subscribers->count() > 5)
80+
<div class="comm:text-xs comm:text-gray-500 comm:dark:text-gray-400 comm:pl-6">
81+
+{{ $this->subscribers->count() - 5 }} more
82+
</div>
83+
@endif
84+
</div>
85+
</div>
86+
@endif
87+
</div>
88+
</div>
89+
</div>
90+

src/Actions/SaveComment.php

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,14 @@ protected function dispatchEvents(Comment $comment): void
4545
UserWasMentionedEvent::dispatch($comment, $mentionee);
4646
});
4747

48+
if (config('commentions.subscriptions.auto_subscribe_on_mention', true)
49+
&& method_exists($comment->commentable, 'subscribe')
50+
) {
51+
$mentionees->each(function (Commenter $mentionee) use ($comment) {
52+
$comment->commentable->subscribe($mentionee);
53+
});
54+
}
55+
4856
$subscribers = method_exists($comment->commentable, 'getSubscribers')
4957
? $comment->commentable->getSubscribers()
5058
: collect();
@@ -65,6 +73,19 @@ protected function dispatchEvents(Comment $comment): void
6573
}
6674
});
6775
}
76+
77+
if (config('commentions.subscriptions.auto_subscribe_on_comment', true)
78+
&& method_exists($comment->commentable, 'subscribe')
79+
) {
80+
// Only subscribe if not already subscribed
81+
if (method_exists($comment->commentable, 'isSubscribed')) {
82+
if (! $comment->commentable->isSubscribed($comment->author)) {
83+
$comment->commentable->subscribe($comment->author);
84+
}
85+
} else {
86+
$comment->commentable->subscribe($comment->author);
87+
}
88+
}
6889
}
6990

7091
public static function run(...$args)

src/CommentionsServiceProvider.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
use Kirschbaum\Commentions\Livewire\CommentList;
1212
use Kirschbaum\Commentions\Livewire\Comments;
1313
use Kirschbaum\Commentions\Livewire\Reactions;
14+
use Kirschbaum\Commentions\Livewire\SubscriptionSidebar;
1415
use Livewire\Livewire;
1516
use Spatie\LaravelPackageTools\Package;
1617
use Spatie\LaravelPackageTools\PackageServiceProvider;
@@ -44,6 +45,7 @@ public function packageBooted(): void
4445
Livewire::component('commentions::comment-list', CommentList::class);
4546
Livewire::component('commentions::comments', Comments::class);
4647
Livewire::component('commentions::reactions', Reactions::class);
48+
Livewire::component('commentions::subscription-sidebar', SubscriptionSidebar::class);
4749

4850
FilamentAsset::register(
4951
[

0 commit comments

Comments
 (0)