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
2 changes: 1 addition & 1 deletion packages/forms/docs/10-rich-editor.md
Original file line number Diff line number Diff line change
Expand Up @@ -724,7 +724,7 @@ RichContentRenderer::make($record->content)

## Registering rich content attributes

There are elements of the rich editor configuration that apply to both the editor and the renderer. For example, if you are using [private images](#using-private-images-in-the-editor), [custom blocks](#using-custom-blocks), [merge tags](#using-merge-tags), [mentions](#using-mentions), or [plugins](#extending-the-rich-editor), you need to ensure that the same configuration is used in both places. To do this, Filament provides you with a way to register rich content attributes that can be used in both the editor and the renderer.
There are elements of the rich editor configuration that apply to both the editor and the renderer. For example, if you are using [private images](#using-private-images-in-the-editor), [custom blocks](#using-custom-blocks), [merge tags](#using-merge-tags), [mentions](#using-mentions), or [plugins](#extending-the-rich-editor), you need to ensure that the same configuration is used in both places. To do this, Filament provides you with a way to register rich content attributes that can be used in both the editor and the renderer. If a plugin implements `HasFileAttachmentProvider`, the file attachment provider is automatically resolved from the plugin, so you do not need to call `fileAttachmentProvider()` on the attribute or on the renderer.

To register rich content attributes on an Eloquent model, you should use the `InteractsWithRichContent` trait and implement the `HasRichContent` interface. This allows you to register the attributes in the `setUpRichContent()` method:

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php

namespace Filament\Forms\Components\RichEditor\Plugins\Contracts;

use Filament\Forms\Components\RichEditor\FileAttachmentProviders\Contracts\FileAttachmentProvider;

interface HasFileAttachmentProvider
{
public function getFileAttachmentProvider(): ?FileAttachmentProvider;
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use Closure;
use Filament\Forms\Components\RichEditor\FileAttachmentProviders\Contracts\FileAttachmentProvider;
use Filament\Forms\Components\RichEditor\Plugins\Contracts\HasFileAttachmentProvider;
use Filament\Forms\Components\RichEditor\Plugins\Contracts\RichContentPlugin;
use Illuminate\Contracts\Support\Htmlable;
use Illuminate\Database\Eloquent\Model;
Expand Down Expand Up @@ -112,7 +113,21 @@ public function fileAttachmentProvider(?FileAttachmentProvider $provider): stati

public function getFileAttachmentProvider(): ?FileAttachmentProvider
{
return $this->fileAttachmentProvider;
if ($this->fileAttachmentProvider) {
return $this->fileAttachmentProvider;
}

foreach ($this->getPlugins() as $plugin) {
if ($plugin instanceof HasFileAttachmentProvider) {
$provider = $plugin->getFileAttachmentProvider();

if ($provider) {
return $this->fileAttachmentProvider = $provider->attribute($this);
}
}
}

return null;
}

public function getModel(): Model
Expand Down
17 changes: 16 additions & 1 deletion packages/forms/src/Components/RichEditor/RichContentRenderer.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use Closure;
use Filament\Forms\Components\RichEditor\FileAttachmentProviders\Contracts\FileAttachmentProvider;
use Filament\Forms\Components\RichEditor\Plugins\Contracts\HasFileAttachmentProvider;
use Filament\Forms\Components\RichEditor\Plugins\Contracts\RichContentPlugin;
use Filament\Forms\Components\RichEditor\TipTapExtensions\CustomBlockExtension;
use Filament\Forms\Components\RichEditor\TipTapExtensions\DetailsContentExtension;
Expand Down Expand Up @@ -453,7 +454,21 @@ public function fileAttachmentProvider(?FileAttachmentProvider $provider): stati

public function getFileAttachmentProvider(): ?FileAttachmentProvider
{
return $this->fileAttachmentProvider;
if ($this->fileAttachmentProvider) {
return $this->fileAttachmentProvider;
}

foreach ($this->plugins as $plugin) {
if ($plugin instanceof HasFileAttachmentProvider) {
$provider = $plugin->getFileAttachmentProvider();

if ($provider) {
return $this->fileAttachmentProvider = $provider;
}
}
}

return null;
}

public function getEditor(): Editor
Expand Down
46 changes: 46 additions & 0 deletions tests/src/Fixtures/Forms/RichEditor/FileAttachmentProviderStub.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<?php

namespace Filament\Tests\Fixtures\Forms\RichEditor;

use Filament\Forms\Components\RichEditor\FileAttachmentProviders\Contracts\FileAttachmentProvider;
use Filament\Forms\Components\RichEditor\RichContentAttribute;
use Livewire\Features\SupportFileUploads\TemporaryUploadedFile;

class FileAttachmentProviderStub implements FileAttachmentProvider
{
public function __construct(
protected ?RichContentAttribute $attribute = null
) {}

public function attribute(RichContentAttribute $attribute): static
{
$this->attribute = $attribute;

return $this;
}

public function getFileAttachmentUrl(mixed $file): ?string
{
return null;
}

public function saveUploadedFileAttachment(TemporaryUploadedFile $file): mixed
{
return null;
}

public function getDefaultFileAttachmentVisibility(): ?string
{
return 'private';
}

public function isExistingRecordRequiredToSaveNewFileAttachments(): bool
{
return false;
}

/**
* @param array<mixed> $exceptIds
*/
public function cleanUpFileAttachments(array $exceptIds): void {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
<?php

namespace Filament\Tests\Fixtures\Forms\RichEditor;

use Filament\Actions\Action;
use Filament\Forms\Components\RichEditor\FileAttachmentProviders\Contracts\FileAttachmentProvider;
use Filament\Forms\Components\RichEditor\Plugins\Contracts\HasFileAttachmentProvider;
use Filament\Forms\Components\RichEditor\Plugins\Contracts\RichContentPlugin;
use Filament\Forms\Components\RichEditor\RichEditorTool;
use Tiptap\Core\Extension;

class PluginWithFileAttachmentProvider implements HasFileAttachmentProvider, RichContentPlugin
{
public function __construct(
protected ?FileAttachmentProvider $fileAttachmentProvider = null
) {
$this->fileAttachmentProvider ??= new FileAttachmentProviderStub;
}

public static function make(?FileAttachmentProvider $fileAttachmentProvider = null): static
{
return new static($fileAttachmentProvider);
}

public function getFileAttachmentProvider(): ?FileAttachmentProvider
{
return $this->fileAttachmentProvider;
}

/**
* @return array<Extension>
*/
public function getTipTapPhpExtensions(): array
{
return [];
}

/**
* @return array<string>
*/
public function getTipTapJsExtensions(): array
{
return [];
}

/**
* @return array<RichEditorTool>
*/
public function getEditorTools(): array
{
return [];
}

/**
* @return array<Action>
*/
public function getEditorActions(): array
{
return [];
}
}
21 changes: 21 additions & 0 deletions tests/src/Fixtures/Models/PostWithRichContent.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

namespace Filament\Tests\Fixtures\Models;

use Filament\Forms\Components\RichEditor\Models\Concerns\InteractsWithRichContent;
use Filament\Forms\Components\RichEditor\Models\Contracts\HasRichContent;
use Filament\Tests\Fixtures\Forms\RichEditor\PluginWithFileAttachmentProvider;

class PostWithRichContent extends Post implements HasRichContent
{
use InteractsWithRichContent;

protected $table = 'posts';

protected function setUpRichContent(): void
{
$this
->registerRichContent('content')
->plugins([PluginWithFileAttachmentProvider::make()]);
}
}
54 changes: 54 additions & 0 deletions tests/src/Forms/Components/RichEditorTest.php
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
<?php

use Filament\Forms\Components\RichEditor;
use Filament\Forms\Components\RichEditor\Plugins\Contracts\HasFileAttachmentProvider;
use Filament\Forms\Components\RichEditor\RichContentRenderer;
use Filament\Schemas\Schema;
use Filament\Tests\Fixtures\Forms\RichEditor\PluginWithFileAttachmentProvider;
use Filament\Tests\Fixtures\Livewire\Livewire;
use Filament\Tests\Fixtures\Models\PostWithRichContent;
use Filament\Tests\TestCase;
use Illuminate\Validation\ValidationException;

Expand Down Expand Up @@ -362,3 +366,53 @@
expect($richEditor->hasFileAttachments())->toBeTrue()
->and($richEditor->hasToolbarButton('attachFiles'))->toBeFalse();
});

test('rich content attribute resolves file attachment provider from plugin implementing `HasFileAttachmentProvider` without calling `fileAttachmentProvider()`', function (): void {
$record = new PostWithRichContent;

$contentAttribute = $record->getRichContentAttribute('content');

$pluginWithProvider = $contentAttribute->getPlugins()[0];

expect($pluginWithProvider)
->toBeInstanceOf(HasFileAttachmentProvider::class);

$expectedProvider = $pluginWithProvider->getFileAttachmentProvider();

expect($contentAttribute->getFileAttachmentProvider())
->toBe($expectedProvider);
});

test('RichEditor receives file attachment provider from rich content attribute when attribute resolves it from plugin', function (): void {
$record = new PostWithRichContent;

$contentAttribute = $record->getRichContentAttribute('content');

$expectedProvider = $contentAttribute->getPlugins()[0]->getFileAttachmentProvider();

$schema = Schema::make(Livewire::make())
->model($record)
->statePath('data')
->components([
RichEditor::make('content'),
]);

$richEditor = $schema->getComponents()[0];

expect($richEditor->getContentAttribute())
->not->toBeNull()
->getFileAttachmentProvider()->toBe($expectedProvider);

expect($richEditor->getFileAttachmentProvider())
->toBe($expectedProvider);
});

test('`RichContentRenderer` resolves file attachment provider from plugin implementing `HasFileAttachmentProvider`', function (): void {
$plugin = PluginWithFileAttachmentProvider::make();

$renderer = RichContentRenderer::make()
->plugins([$plugin]);

expect($renderer->getFileAttachmentProvider())
->toBe($plugin->getFileAttachmentProvider());
});
Loading