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
153 changes: 152 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,11 @@ A forum package for Filament apps that provides both admin and frontend resource
}
```

5. Add `HasFavoriteForumPost` trait to your User model:
5. User model requirements

- Ensure your User model has a `name` attribute

- Add `HasFavoriteForumPost` trait to your `User` model:

```php
use Tapp\FilamentForum\Models\Traits\HasFavoriteForumPost;
Expand All @@ -85,6 +89,17 @@ class User extends Authenticatable
}
```

- Add `HasMentionables` trait (you can use it to customize which users are mentionable, see below in "Custom Mentionables") to your `User` model

```php
use Tapp\FilamentForum\Models\Traits\HasMentionables;

class User extends Authenticatable
{
use HasMentionables;
}
```

6. Add to your custom theme (usually`theme.css`) file:

To include the TailwindCSS styles used on frontend pages, add to your theme file:
Expand Down Expand Up @@ -181,6 +196,141 @@ class User extends Authenticatable
}
```

## Custom Mentionables

You can customize which users are mentionable by overriding the `getMentionableUsers()` method in your `User` model:

```php
// In your User model
public static function getMentionableUsers()
{
// Only active users
return static::where('is_active', true)->get();
}
```

## User Avatar

Optionally, implements Filament's `HasAvatar` interface:

```php
use Filament\Models\Contracts\HasAvatar;

class User extends Authenticatable implements HasAvatar
{
public function getFilamentAvatarUrl(): ?string
{
return $this->avatar_url;
}
}
```

## Events Dispatched

The plugin automatically dispatches events when a forum is created, a comment is created, reacted to, or when users are mentioned in a comment:

- `Tapp\FilamentForum\Events\ForumCommentCreated`
- `Tapp\FilamentForum\Events\ForumPostCreated`
- `Tapp\FilamentForum\Events\CommentWasReacted`
- `Tapp\FilamentForum\Events\UserWasMentioned`

### UserWasMentioned Event

You can send a notification by listening to the `UserWasMentioned` event. The event structure is:

```php
use Tapp\FilamentForum\Events\UserWasMentioned;

// The event contains:
// - $mentionedUser: The user who was mentioned
// - $comment: The ForumComment instance
```

Example usage:

```php
namespace App\Listeners;

use App\Notifications\UserMentionedNotification;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Notification;
use Illuminate\Queue\InteractsWithQueue;
use Tapp\FilamentForum\Events\UserWasMentioned;

class SendUserMentionedNotification implements ShouldQueue
{
use InteractsWithQueue;

public function handle(UserWasMentioned $event): void
{
$mentionedUser = $event->mentionedUser;
$comment = $event->comment;

// Send notification to the mentioned user
$mentionedUser->notify(new UserMentionedNotification($comment));

// Or dispatch a custom notification
Notification::make()
->title('You were mentioned in a comment')
->body("You were mentioned by {$comment->author->name}")
->sendToDatabase($mentionedUser);
}
}
```

This should work with [Laravel's event auto-discovery](https://laravel.com/docs/11.x/events#registering-events-and-listeners). If not, you can register your listener on `EventServiceProvider`:

```php
use Tapp\FilamentForum\Events\UserWasMentioned;
use App\Listeners\SendUserMentionedNotification;

protected $listen = [
UserWasMentioned::class => [
SendUserMentionedNotification::class,
],
];
```

Custom notification example:

```php
namespace App\Notifications;

use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;
use Tapp\FilamentForum\Models\ForumComment;

class UserMentionedNotification extends Notification
{
public function __construct(
public ForumComment $comment
) {}

public function via($notifiable)
{
return ['database', 'mail'];
}

public function toDatabase($notifiable)
{
return [
'title' => 'You were mentioned',
'body' => "You were mentioned in a comment by {$this->comment->author->name}",
'comment_id' => $this->comment->id,
'forum_post_id' => $this->comment->forumPost->id,
];
}

public function toMail($notifiable)
{
return (new MailMessage)
->subject('You were mentioned in a forum comment')
->line("You were mentioned in a comment by {$this->comment->author->name}")
->action('View Comment', route('forum.posts.show', $this->comment->forumPost));
}
}
```

## Changelog

Please see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently.
Expand All @@ -196,6 +346,7 @@ If you discover any security-related issues, please email security@tappnetwork.c
## Credits

- [Tapp Network](https://github.com/TappNetwork)
- Comments inspired by [Commentions](https://github.com/kirschbaum-development/commentions)
- [All Contributors](../../contributors)

## License
Expand Down
81 changes: 81 additions & 0 deletions bin/build.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
#!/usr/bin/env node

import * as esbuild from 'esbuild'
import { copyFileSync, existsSync, mkdirSync } from 'fs'
import { dirname } from 'path'

const isDev = process.argv.includes('--dev')

async function compile(options) {
const context = await esbuild.context(options)

if (isDev) {
await context.watch()
} else {
await context.rebuild()
await context.dispose()
}
}

const defaultOptions = {
define: {
'process.env.NODE_ENV': isDev ? `'development'` : `'production'`,
},
bundle: true,
mainFields: ['module', 'main'],
platform: 'neutral',
sourcemap: isDev ? 'inline' : false,
sourcesContent: isDev,
treeShaking: true,
target: ['es2020'],
minify: !isDev,
plugins: [{
name: 'watchPlugin',
setup(build) {
build.onStart(() => {
console.log(`Build started at ${new Date(Date.now()).toLocaleTimeString()}: ${build.initialOptions.outfile}`)
})

build.onEnd((result) => {
if (result.errors.length > 0) {
console.log(`Build failed at ${new Date(Date.now()).toLocaleTimeString()}: ${build.initialOptions.outfile}`, result.errors)
} else {
console.log(`Build finished at ${new Date(Date.now()).toLocaleTimeString()}: ${build.initialOptions.outfile}`)
}
})
}
}],
}

// Ensure dist directory exists
const distDir = 'resources/dist'
if (!existsSync(distDir)) {
mkdirSync(distDir, { recursive: true })
}

// Copy CSS file (no compilation needed)
const cssSource = 'resources/css/filament-forum.css'
const cssDest = 'resources/dist/filament-forum.css'

if (existsSync(cssSource)) {
copyFileSync(cssSource, cssDest)
console.log('✓ CSS copied to dist/filament-forum.css')
} else {
console.error('✗ CSS source file not found')
}

// Compile JavaScript with esbuild
compile({
...defaultOptions,
entryPoints: ['./resources/js/components/forum-mentions.js'],
outfile: './resources/dist/forum-mentions.js',
})

compile({
...defaultOptions,
entryPoints: ['./resources/js/tiptap/mention.js'],
outfile: './resources/dist/tiptap-mention.js',
format: 'esm', // Output as ES module for Filament to import
})

console.log('Build complete!')
1 change: 0 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
"require": {
"php": ">=8.3",
"illuminate/contracts": "^10.0||^11.0||^12.0",
"kirschbaum-development/commentions": "^0.6",
"filament/filament": "^4.0",
"spatie/laravel-package-tools": "^1.0",
"spatie/laravel-medialibrary": "^11.0",
Expand Down
33 changes: 33 additions & 0 deletions database/migrations/create_forum_comment_reactions_table.php.stub
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
public function up(): void
{
Schema::create('forum_comment_reactions', function (Blueprint $table) {
$table->id();
$table->foreignId('forum_comment_id')->constrained()->cascadeOnDelete();
$table->morphs('reactor');

if (config('database.default') === 'mysql') {
$table->string('type', 50)->collation('utf8mb4_bin');
} else {
$table->string('type', 50);
}

$table->timestamps();

$table->unique(['forum_comment_id', 'reactor_type', 'reactor_id', 'type'], 'unique_comment_reaction');
$table->index(['forum_comment_id', 'type']);
});
}

public function down(): void
{
Schema::dropIfExists('forum_comment_reactions');
}
};
26 changes: 26 additions & 0 deletions database/migrations/create_forum_comments_table.php.stub
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
public function up(): void
{
Schema::create('forum_comments', function (Blueprint $table) {
$table->id();
$table->foreignId('forum_post_id')->constrained()->cascadeOnDelete();
$table->morphs('author');
$table->longText('content');
$table->timestamps();

$table->index(['forum_post_id', 'created_at']);
});
}

public function down(): void
{
Schema::dropIfExists('forum_comments');
}
};
Loading