Files
relaticle-comments/src/CommentsConfig.php
manukminasyan e173d9b4dd refactor: replace custom textarea with Filament RichEditor and built-in mentions
Replace the custom Alpine.js textarea + mention system with Filament v5's
built-in RichEditor component and MentionProvider. This fixes Alpine scope
errors (showMentions/mentionResults not defined) that occurred during
Livewire DOM morphing inside Filament slide-over modals.

- Add InteractsWithForms + HasForms to Comments and CommentItem components
- Define commentForm(), editForm(), replyForm() with RichEditor + mentions
- Add CommentsConfig::makeMentionProvider() shared helper
- Update MentionParser to extract mention IDs from RichEditor HTML format
- Update Comment::renderBodyWithMentions() to use RichContentRenderer
- Remove all custom Alpine.js mention code from blade templates
- Backward compatible with existing plain text comments
2026-03-27 18:43:07 +04:00

193 lines
5.6 KiB
PHP

<?php
namespace Relaticle\Comments;
use App\Models\User;
use Closure;
use Filament\Forms\Components\RichEditor\MentionProvider;
use Relaticle\Comments\Mentions\DefaultMentionResolver;
use Relaticle\Comments\Models\Comment;
use Relaticle\Comments\Policies\CommentPolicy;
class CommentsConfig
{
protected static ?Closure $resolveAuthenticatedUser = null;
public static function getCommentModel(): string
{
return config('comments.models.comment', Comment::class);
}
public static function getCommenterModel(): string
{
return config('comments.commenter.model', User::class);
}
public static function getCommentTable(): string
{
return static::getTableName('comments');
}
public static function getTableName(string $table): string
{
$defaults = [
'comments' => 'comments',
'reactions' => 'comment_reactions',
'mentions' => 'comment_mentions',
'subscriptions' => 'comment_subscriptions',
'attachments' => 'comment_attachments',
];
return config("comments.table_names.{$table}", $defaults[$table] ?? $table);
}
public static function getCommenterMorphName(): string
{
return config('comments.column_names.commenter_morph', 'commenter');
}
public static function getMaxDepth(): int
{
return (int) config('comments.threading.max_depth', 2);
}
public static function getPerPage(): int
{
return (int) config('comments.pagination.per_page', 10);
}
/** @return array<int, array<int, string>> */
public static function getEditorToolbar(): array
{
return (array) config('comments.editor.toolbar', [
['bold', 'italic', 'strike', 'link'],
['bulletList', 'orderedList'],
['codeBlock'],
]);
}
public static function getPolicyClass(): string
{
return config('comments.policy', CommentPolicy::class);
}
public static function getMentionResolver(): string
{
return config('comments.mentions.resolver', DefaultMentionResolver::class);
}
public static function getMentionMaxResults(): int
{
return (int) config('comments.mentions.max_results', 5);
}
/** @return array<string, string> */
public static function getReactionEmojiSet(): array
{
return (array) config('comments.reactions.emoji_set', [
'thumbs_up' => "\u{1F44D}",
'heart' => "\u{2764}\u{FE0F}",
'celebrate' => "\u{1F389}",
'laugh' => "\u{1F604}",
'thinking' => "\u{1F914}",
'sad' => "\u{1F622}",
]);
}
/** @return array<int, string> */
public static function getAllowedReactions(): array
{
return array_keys(static::getReactionEmojiSet());
}
/** @return array<int, string> */
public static function getNotificationChannels(): array
{
return (array) config('comments.notifications.channels', ['database']);
}
public static function areNotificationsEnabled(): bool
{
return (bool) config('comments.notifications.enabled', true);
}
public static function shouldAutoSubscribe(): bool
{
return (bool) config('comments.subscriptions.auto_subscribe', true);
}
public static function areAttachmentsEnabled(): bool
{
return (bool) config('comments.attachments.enabled', true);
}
public static function getAttachmentDisk(): string
{
return (string) config('comments.attachments.disk', 'public');
}
public static function getAttachmentMaxSize(): int
{
return (int) config('comments.attachments.max_size', 10240);
}
/** @return array<int, string> */
public static function getAttachmentAllowedTypes(): array
{
return (array) config('comments.attachments.allowed_types', [
'image/jpeg',
'image/png',
'image/gif',
'image/webp',
'application/pdf',
'text/plain',
'application/msword',
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
]);
}
public static function isBroadcastingEnabled(): bool
{
return (bool) config('comments.broadcasting.enabled', false);
}
public static function getBroadcastChannelPrefix(): string
{
return (string) config('comments.broadcasting.channel_prefix', 'comments');
}
public static function getPollingInterval(): string
{
return (string) config('comments.polling.interval', '10s');
}
public static function resolveAuthenticatedUser(): ?object
{
if (static::$resolveAuthenticatedUser) {
return call_user_func(static::$resolveAuthenticatedUser);
}
return auth()->user();
}
public static function resolveAuthenticatedUserUsing(Closure $callback): void
{
static::$resolveAuthenticatedUser = $callback;
}
public static function makeMentionProvider(): MentionProvider
{
return MentionProvider::make('@')
->getSearchResultsUsing(fn (string $search): array => static::getCommenterModel()::query()
->where('name', 'like', "%{$search}%")
->orderBy('name')
->limit(static::getMentionMaxResults())
->pluck('name', 'id')
->all())
->getLabelsUsing(fn (array $ids): array => static::getCommenterModel()::query()
->whereIn('id', $ids)
->pluck('name', 'id')
->all());
}
}