- CanComment trait replaces IsCommenter - Commentator interface replaces Commenter - Models moved to Models\ namespace (Comment, Reaction, Attachment, Subscription) - commenter_type/commenter_id columns replace user_type/user_id - CommentsConfig replaces Config class - table_names config key replaces tables - getCommentDisplayName() replaces getCommentName()
286 lines
8.0 KiB
Markdown
286 lines
8.0 KiB
Markdown
---
|
|
name: comments-development
|
|
description: Full-featured commenting system for Filament panels with polymorphic comments, threaded replies, @mentions, emoji reactions, file attachments, and real-time notifications. Use when adding the HasComments trait to models, integrating CommentsAction/CommentsTableAction/CommentsEntry in Filament, configuring threading, reactions, mentions, attachments, notifications, broadcasting, or customizing the CommentPolicy.
|
|
---
|
|
|
|
# Comments Development
|
|
|
|
## When to Use This Skill
|
|
|
|
Use when:
|
|
- Adding commenting capability to an Eloquent model
|
|
- Integrating comments into Filament resources (actions, table actions, infolists)
|
|
- Configuring threading depth, reactions, mentions, or attachments
|
|
- Working with comment notifications and subscriptions
|
|
- Customizing the CommentPolicy for authorization
|
|
- Implementing real-time updates via broadcasting or polling
|
|
- Creating a custom MentionResolver
|
|
|
|
## Quick Start
|
|
|
|
### 1. Add Traits to Models
|
|
|
|
```php
|
|
use Relaticle\Comments\Concerns\HasComments;
|
|
use Relaticle\Comments\Contracts\Commentable;
|
|
|
|
class Project extends Model implements Commentable
|
|
{
|
|
use HasComments;
|
|
}
|
|
```
|
|
|
|
```php
|
|
use Relaticle\Comments\Concerns\CanComment;
|
|
use Relaticle\Comments\Contracts\Commentator;
|
|
|
|
class User extends Authenticatable implements Commentator
|
|
{
|
|
use CanComment;
|
|
}
|
|
```
|
|
|
|
### 2. Register Plugin in Panel
|
|
|
|
```php
|
|
use Relaticle\Comments\CommentsPlugin;
|
|
|
|
public function panel(Panel $panel): Panel
|
|
{
|
|
return $panel
|
|
->plugins([
|
|
CommentsPlugin::make(),
|
|
]);
|
|
}
|
|
```
|
|
|
|
### 3. Publish and Run Migrations
|
|
|
|
```bash
|
|
php artisan vendor:publish --tag=comments-migrations
|
|
php artisan migrate
|
|
```
|
|
|
|
## Filament Integration
|
|
|
|
### Slide-Over Action (View/Edit Pages)
|
|
|
|
```php
|
|
use Relaticle\Comments\Filament\Actions\CommentsAction;
|
|
|
|
protected function getHeaderActions(): array
|
|
{
|
|
return [
|
|
CommentsAction::make(),
|
|
];
|
|
}
|
|
```
|
|
|
|
Shows a comment count badge and opens a slide-over modal with the full comment thread.
|
|
|
|
### Table Row Action
|
|
|
|
```php
|
|
use Relaticle\Comments\Filament\Actions\CommentsTableAction;
|
|
|
|
public static function table(Table $table): Table
|
|
{
|
|
return $table
|
|
->actions([
|
|
CommentsTableAction::make(),
|
|
]);
|
|
}
|
|
```
|
|
|
|
### Infolist Entry
|
|
|
|
```php
|
|
use Relaticle\Comments\Filament\Infolists\Components\CommentsEntry;
|
|
|
|
public static function infolist(Infolist $infolist): Infolist
|
|
{
|
|
return $infolist->schema([
|
|
CommentsEntry::make('comments'),
|
|
]);
|
|
}
|
|
```
|
|
|
|
Embeds the full comment component inline within an infolist.
|
|
|
|
## Configuration Reference
|
|
|
|
Publish config: `php artisan vendor:publish --tag=comments-config`
|
|
|
|
| Key | Default | Purpose |
|
|
|-----|---------|---------|
|
|
| `table_names.comments` | `'comments'` | Main comments table name |
|
|
| `models.comment` | `Comment::class` | Comment model class |
|
|
| `commenter.model` | `User::class` | Commenter (user) model class |
|
|
| `policy` | `CommentPolicy::class` | Authorization policy class |
|
|
| `threading.max_depth` | `2` | Maximum reply nesting depth |
|
|
| `pagination.per_page` | `10` | Comments per page |
|
|
| `reactions.emoji_set` | 6 emojis | Available reaction emojis |
|
|
| `mentions.resolver` | `DefaultMentionResolver::class` | User search resolver |
|
|
| `mentions.max_results` | `5` | Autocomplete results limit |
|
|
| `editor.toolbar` | bold, italic, strike, link, lists, codeBlock | Rich text toolbar buttons |
|
|
| `notifications.enabled` | `true` | Enable/disable notifications |
|
|
| `notifications.channels` | `['database']` | Notification channels |
|
|
| `subscriptions.auto_subscribe` | `true` | Auto-subscribe on comment/mention |
|
|
| `attachments.enabled` | `true` | Enable file attachments |
|
|
| `attachments.disk` | `'public'` | Storage disk |
|
|
| `attachments.max_size` | `10240` | Max file size (KB) |
|
|
| `attachments.allowed_types` | images, pdf, text, word | Allowed MIME types |
|
|
| `broadcasting.enabled` | `false` | Enable real-time broadcasting |
|
|
| `broadcasting.channel_prefix` | `'comments'` | Private channel prefix |
|
|
| `polling.interval` | `'10s'` | Livewire polling interval (fallback) |
|
|
|
|
### Default Reactions
|
|
|
|
```php
|
|
'reactions' => [
|
|
'emoji_set' => [
|
|
'thumbs_up' => ['emoji' => "\u{1F44D}", 'label' => 'Like'],
|
|
'heart' => ['emoji' => "\u{2764}\u{FE0F}", 'label' => 'Love'],
|
|
'celebrate' => ['emoji' => "\u{1F389}", 'label' => 'Celebrate'],
|
|
'laugh' => ['emoji' => "\u{1F602}", 'label' => 'Laugh'],
|
|
'thinking' => ['emoji' => "\u{1F914}", 'label' => 'Thinking'],
|
|
'sad' => ['emoji' => "\u{1F622}", 'label' => 'Sad'],
|
|
],
|
|
],
|
|
```
|
|
|
|
## Events
|
|
|
|
| Event | Broadcast | Payload |
|
|
|-------|-----------|---------|
|
|
| `CommentCreated` | Yes | `$comment` |
|
|
| `CommentUpdated` | Yes | `$comment` |
|
|
| `CommentDeleted` | Yes | `$comment` |
|
|
| `CommentReacted` | Yes | `$comment`, `$user`, `$reaction`, `$action` (added/removed) |
|
|
| `UserMentioned` | No | `$comment`, `$mentionedUser` |
|
|
|
|
Broadcasting uses private channels: `{prefix}.{commentable_type}.{commentable_id}`
|
|
|
|
## Authorization
|
|
|
|
Default `CommentPolicy` methods:
|
|
|
|
| Method | Default | Description |
|
|
|--------|---------|-------------|
|
|
| `viewAny()` | `true` | Everyone can view comments |
|
|
| `create()` | `true` | Everyone can create comments |
|
|
| `update()` | Owner only | Only comment author can edit |
|
|
| `delete()` | Owner only | Only comment author can delete |
|
|
| `reply()` | Depth check | Can reply if max_depth not exceeded |
|
|
|
|
### Custom Policy
|
|
|
|
```php
|
|
// config/comments.php
|
|
'policy' => App\Policies\CustomCommentPolicy::class,
|
|
```
|
|
|
|
```php
|
|
namespace App\Policies;
|
|
|
|
use Relaticle\Comments\Models\Comment;
|
|
use Relaticle\Comments\Contracts\Commentator;
|
|
|
|
class CustomCommentPolicy
|
|
{
|
|
public function delete(Commentator $user, Comment $comment): bool
|
|
{
|
|
return $comment->commenter_id === $user->getKey()
|
|
|| $user->hasRole('admin');
|
|
}
|
|
}
|
|
```
|
|
|
|
## Common Patterns
|
|
|
|
### Scoped Comments (Multi-tenancy)
|
|
|
|
```php
|
|
use Relaticle\Comments\CommentsConfig;
|
|
|
|
// In AppServiceProvider::boot()
|
|
CommentsConfig::resolveAuthenticatedUserUsing(function () {
|
|
return auth()->user();
|
|
});
|
|
```
|
|
|
|
### Custom Mention Resolver
|
|
|
|
```php
|
|
use Relaticle\Comments\Contracts\MentionResolver;
|
|
|
|
class TeamMentionResolver implements MentionResolver
|
|
{
|
|
public function search(string $query): Collection
|
|
{
|
|
return User::query()
|
|
->where('team_id', auth()->user()->team_id)
|
|
->where('name', 'like', "{$query}%")
|
|
->limit(config('comments.mentions.max_results'))
|
|
->get();
|
|
}
|
|
|
|
public function resolveByNames(array $names): Collection
|
|
{
|
|
return User::query()
|
|
->where('team_id', auth()->user()->team_id)
|
|
->whereIn('name', $names)
|
|
->get();
|
|
}
|
|
}
|
|
```
|
|
|
|
Register in config:
|
|
|
|
```php
|
|
// config/comments.php
|
|
'mentions' => [
|
|
'resolver' => App\Comments\TeamMentionResolver::class,
|
|
],
|
|
```
|
|
|
|
### Enable Broadcasting
|
|
|
|
```php
|
|
// config/comments.php
|
|
'broadcasting' => [
|
|
'enabled' => true,
|
|
'channel_prefix' => 'comments',
|
|
],
|
|
```
|
|
|
|
When broadcasting is enabled, the Livewire component listens for real-time events. When disabled, it falls back to polling at the configured interval.
|
|
|
|
## Database Schema
|
|
|
|
Five tables are created:
|
|
|
|
- `comments` -- Polymorphic comments with parent_id for threading, soft deletes
|
|
- `comment_reactions` -- Unique reactions per user+comment+emoji
|
|
- `comment_mentions` -- Tracks @mentioned users per comment
|
|
- `comment_subscriptions` -- Thread subscription tracking per user+commentable
|
|
- `comment_attachments` -- File metadata (path, name, MIME type, size, disk)
|
|
|
|
## Model Relationships
|
|
|
|
```php
|
|
// On any Commentable model
|
|
$model->comments(); // All comments (morphMany)
|
|
$model->topLevelComments(); // Top-level only (no parent)
|
|
$model->commentCount(); // Total count
|
|
|
|
// On Comment model
|
|
$comment->commentable(); // Parent model (morphTo)
|
|
$comment->commenter(); // Commenter (morphTo)
|
|
$comment->parent(); // Parent comment (belongsTo)
|
|
$comment->replies(); // Child comments (hasMany)
|
|
$comment->reactions(); // Reactions (hasMany)
|
|
$comment->attachments(); // File attachments (hasMany)
|
|
$comment->mentions(); // Mentioned users (morphToMany)
|
|
```
|