Files
relaticle-comments/src/CommentsServiceProvider.php
ilyapashayan 541d11ab90 fix: preserve mention data attributes through HTML sanitization
Filament's sanitizer strips data-id, data-label and data-char from
mention spans, breaking both display (unstyled @mention) and editing
(@-only shown in RichEditor). Register a package-scoped sanitizer that
explicitly allows these attributes on span elements.

Also fix double-replacement bug in renderBodyWithMentions() where both
the rich-editor regex and str_replace fallback could run on the same
mention, producing nested styled spans.
2026-04-01 01:10:05 +04:00

98 lines
3.6 KiB
PHP

<?php
namespace Relaticle\Comments;
use Filament\Support\Assets\Css;
use Filament\Support\Facades\FilamentAsset;
use Illuminate\Database\Eloquent\Relations\Relation;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\Facades\Gate;
use Livewire\Livewire;
use Relaticle\Comments\Contracts\MentionResolver;
use Relaticle\Comments\Events\CommentCreated;
use Relaticle\Comments\Events\UserMentioned;
use Relaticle\Comments\Listeners\SendCommentRepliedNotification;
use Relaticle\Comments\Listeners\SendUserMentionedNotification;
use Relaticle\Comments\Livewire\CommentItem;
use Relaticle\Comments\Livewire\Comments;
use Relaticle\Comments\Livewire\Reactions;
use Spatie\LaravelPackageTools\Package;
use Spatie\LaravelPackageTools\PackageServiceProvider;
use Symfony\Component\HtmlSanitizer\HtmlSanitizer;
use Symfony\Component\HtmlSanitizer\HtmlSanitizerConfig;
class CommentsServiceProvider extends PackageServiceProvider
{
public static string $name = 'comments';
public static string $viewNamespace = 'comments';
public function configurePackage(Package $package): void
{
$package
->name(static::$name)
->hasConfigFile()
->hasViews(static::$viewNamespace)
->hasTranslations()
->hasMigrations([
'create_comments_table',
'create_comment_mentions_table',
'create_comment_reactions_table',
'create_comment_subscriptions_table',
'create_comment_attachments_table',
]);
}
public function packageRegistered(): void
{
Relation::morphMap([
'comment' => CommentsConfig::getCommentModel(),
]);
$this->app->bind(
MentionResolver::class,
fn () => new (CommentsConfig::getMentionResolver())
);
$this->app->scoped(
'comments.html_sanitizer',
fn (): HtmlSanitizer => new HtmlSanitizer(
(new HtmlSanitizerConfig)
->allowSafeElements()
->allowRelativeLinks()
->allowRelativeMedias()
->allowAttribute('class', allowedElements: '*')
->allowAttribute('data-color', allowedElements: '*')
->allowAttribute('data-from-breakpoint', allowedElements: '*')
->allowAttribute('data-type', allowedElements: '*')
->allowAttribute('data-id', allowedElements: 'span')
->allowAttribute('data-label', allowedElements: 'span')
->allowAttribute('data-char', allowedElements: 'span')
->allowAttribute('style', allowedElements: '*')
->allowAttribute('width', allowedElements: 'img')
->allowAttribute('height', allowedElements: 'img')
->withMaxInputLength(500000)
),
);
}
public function packageBooted(): void
{
Gate::policy(
CommentsConfig::getCommentModel(),
CommentsConfig::getPolicyClass(),
);
Event::listen(CommentCreated::class, SendCommentRepliedNotification::class);
Event::listen(UserMentioned::class, SendUserMentionedNotification::class);
Livewire::component('comments', Comments::class);
Livewire::component('comment-item', CommentItem::class);
Livewire::component('reactions', Reactions::class);
FilamentAsset::register([
Css::make('comments', __DIR__.'/../resources/css/comments.css'),
], 'relaticle/comments');
}
}