From 78b89025f2a0ac9248219e1de6cf9f3c767bf716 Mon Sep 17 00:00:00 2001 From: Malik Alleyne-Jones Date: Tue, 12 May 2026 10:46:35 -0400 Subject: [PATCH 1/3] feat: resolve author avatar via Filament panel provider (#49) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds an `avatar_provider` config key and a fallback chain in `Comment::getAuthorAvatar()`: 1. `HasAvatar::getFilamentAvatarUrl()` (existing behavior) 2. `config('commentions.avatar_provider')` if set 3. Current Filament panel's `getDefaultAvatarProvider()` if a panel is active 4. ui-avatars.com fallback (existing behavior) Any class exposing `get(Model|Authenticatable $user): string` works — including Filament's first-party `GravatarProvider` and `UiAvatarsProvider`. --- README.md | 14 +++++++++++ config/commentions.php | 17 +++++++++++++ src/Comment.php | 37 +++++++++++++++++++++++++---- src/Config.php | 5 ++++ tests/AvatarProviderTest.php | 28 ++++++++++++++++++++++ tests/Models/StubAvatarProvider.php | 14 +++++++++++ 6 files changed, 111 insertions(+), 4 deletions(-) create mode 100644 tests/AvatarProviderTest.php create mode 100644 tests/Models/StubAvatarProvider.php diff --git a/README.md b/README.md index 354711c..a3c1cf1 100644 --- a/README.md +++ b/README.md @@ -396,6 +396,20 @@ class User extends Authenticatable implements Commenter, HasName, HasAvatar } ``` +If your users do not implement `HasAvatar`, Commentions will consult an avatar provider before falling back to `ui-avatars.com`. By default it uses the current Filament panel's default provider (set via `Panel::defaultAvatarProvider(...)`). To force a specific provider regardless of panel context, set the `avatar_provider` config key: + +```php +// config/commentions.php +use Filament\AvatarProviders\GravatarProvider; + +return [ + // ... + 'avatar_provider' => GravatarProvider::class, +]; +``` + +Any class exposing a `get(Model|Authenticatable $user): string` method works. + ### Customizing TipTap Editor Styles You can customize the TipTap editor CSS classes used using the `Config` class. diff --git a/config/commentions.php b/config/commentions.php index 4267451..6a9a13a 100644 --- a/config/commentions.php +++ b/config/commentions.php @@ -46,6 +46,23 @@ 'allowed' => ['👍', '❤️', '😂', '😮', '😢', '🤔'], ], + /* + |-------------------------------------------------------------------------- + | Avatar provider + |-------------------------------------------------------------------------- + | + | Class name of a Filament-compatible avatar provider used to resolve a + | URL for the comment author when the user does not implement HasAvatar. + | Must expose a `get(Model|Authenticatable $user): string` method. + | Examples: Filament\AvatarProviders\UiAvatarsProvider, + | Filament\AvatarProviders\GravatarProvider. + | + | When null, Commentions falls back to the active Filament panel's + | default avatar provider (if any), and finally to ui-avatars.com. + | + */ + 'avatar_provider' => null, + /* |-------------------------------------------------------------------------- | Subscriptions diff --git a/src/Comment.php b/src/Comment.php index 91eca58..d5f49ad 100644 --- a/src/Comment.php +++ b/src/Comment.php @@ -121,14 +121,18 @@ public function getAuthorName(): string public function getAuthorAvatar(): string { - $avatar = null; - if ($this->author instanceof HasAvatar) { $avatar = $this->author->getFilamentAvatarUrl(); + + if (! is_null($avatar)) { + return $avatar; + } } - if (! is_null($avatar)) { - return $avatar; + $providerAvatar = $this->resolveAvatarFromProvider(); + + if (! is_null($providerAvatar)) { + return $providerAvatar; } $name = str(Manager::getName($this->author)) @@ -140,6 +144,31 @@ public function getAuthorAvatar(): string return 'https://ui-avatars.com/api/?name=' . urlencode($name) . '&color=FFFFFF&background=71717b'; } + protected function resolveAvatarFromProvider(): ?string + { + $providerClass = Config::getAvatarProvider(); + + if ($providerClass === null) { + try { + if (\Filament\Facades\Filament::getCurrentPanel() !== null) { + $providerClass = \Filament\Facades\Filament::getDefaultAvatarProvider(); + } + } catch (\Throwable) { + return null; + } + } + + if ($providerClass === null) { + return null; + } + + try { + return app($providerClass)->get($this->author); + } catch (\Throwable) { + return null; + } + } + public function getBody(): string { return $this->body; diff --git a/src/Config.php b/src/Config.php index 052a2e2..336d169 100644 --- a/src/Config.php +++ b/src/Config.php @@ -82,6 +82,11 @@ public static function getAllowedReactions(): array return config('commentions.reactions.allowed', ['👍']); } + public static function getAvatarProvider(): ?string + { + return config('commentions.avatar_provider'); + } + public static function resolveTipTapCssClassesUsing(Closure $callback): void { static::$resolveTipTapCssClasses = $callback; diff --git a/tests/AvatarProviderTest.php b/tests/AvatarProviderTest.php new file mode 100644 index 0000000..2f90b2b --- /dev/null +++ b/tests/AvatarProviderTest.php @@ -0,0 +1,28 @@ +set('commentions.avatar_provider', null); + + $user = User::factory()->create(['name' => 'Jane Doe']); + $post = Post::factory()->create(); + $comment = CommentModel::factory()->author($user)->commentable($post)->create(); + + expect($comment->getAuthorAvatar()) + ->toStartWith('https://ui-avatars.com/api/?'); +}); + +test('uses configured avatar provider when set', function () { + config()->set('commentions.avatar_provider', StubAvatarProvider::class); + + $user = User::factory()->create(['name' => 'Jane Doe']); + $post = Post::factory()->create(); + $comment = CommentModel::factory()->author($user)->commentable($post)->create(); + + expect($comment->getAuthorAvatar()) + ->toBe('https://stub.test/avatar/Jane%20Doe'); +}); diff --git a/tests/Models/StubAvatarProvider.php b/tests/Models/StubAvatarProvider.php new file mode 100644 index 0000000..7a13da3 --- /dev/null +++ b/tests/Models/StubAvatarProvider.php @@ -0,0 +1,14 @@ +name); + } +} From 677ab350a720856aa5d78fc7ac580d9899700acb Mon Sep 17 00:00:00 2001 From: Malik Alleyne-Jones Date: Sat, 16 May 2026 19:52:54 -0400 Subject: [PATCH 2/3] Update AvatarProviderTest.php --- tests/AvatarProviderTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/AvatarProviderTest.php b/tests/AvatarProviderTest.php index 2f90b2b..9bf86db 100644 --- a/tests/AvatarProviderTest.php +++ b/tests/AvatarProviderTest.php @@ -24,5 +24,5 @@ $comment = CommentModel::factory()->author($user)->commentable($post)->create(); expect($comment->getAuthorAvatar()) - ->toBe('https://stub.test/avatar/Jane%20Doe'); + ->toBe('https://stub.test/avatar/Jane+Doe'); }); From 20e9e64c2ca99fd465502693658aa569fb76e007 Mon Sep 17 00:00:00 2001 From: Malik Alleyne-Jones Date: Sun, 31 May 2026 18:08:28 -0400 Subject: [PATCH 3/3] Update Comment.php --- src/Comment.php | 51 +++++++++++++++++++++++++------------------------ 1 file changed, 26 insertions(+), 25 deletions(-) diff --git a/src/Comment.php b/src/Comment.php index d5f49ad..abab6b0 100644 --- a/src/Comment.php +++ b/src/Comment.php @@ -6,6 +6,7 @@ use Carbon\CarbonInterface; use Closure; use DateTime; +use Filament\Facades\Filament; use Filament\Models\Contracts\HasAvatar; use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Factories\HasFactory; @@ -144,31 +145,6 @@ public function getAuthorAvatar(): string return 'https://ui-avatars.com/api/?name=' . urlencode($name) . '&color=FFFFFF&background=71717b'; } - protected function resolveAvatarFromProvider(): ?string - { - $providerClass = Config::getAvatarProvider(); - - if ($providerClass === null) { - try { - if (\Filament\Facades\Filament::getCurrentPanel() !== null) { - $providerClass = \Filament\Facades\Filament::getDefaultAvatarProvider(); - } - } catch (\Throwable) { - return null; - } - } - - if ($providerClass === null) { - return null; - } - - try { - return app($providerClass)->get($this->author); - } catch (\Throwable) { - return null; - } - } - public function getBody(): string { return $this->body; @@ -212,6 +188,31 @@ public function getContentHash(): string ])); } + protected function resolveAvatarFromProvider(): ?string + { + $providerClass = Config::getAvatarProvider(); + + if ($providerClass === null) { + try { + if (Filament::getCurrentPanel() !== null) { + $providerClass = Filament::getDefaultAvatarProvider(); + } + } catch (\Throwable) { + return null; + } + } + + if ($providerClass === null) { + return null; + } + + try { + return app($providerClass)->get($this->author); + } catch (\Throwable) { + return null; + } + } + protected static function newFactory() { return CommentFactory::new();