Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 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
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,5 @@ phpunit.xml
phpstan.neon
testbench.yaml
vendor
.phpunit.cache
.phpunit.cache
CLAUDE.md
128 changes: 128 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
- [disabling block in select block](#disabling-block-in-select-block)
- [iframe resizing](#iframe-resizing)
- [Parameter injection](#parameter-injection)
- [Global blocks](#global-blocks)
- [Rendering page builder items on infolist](#rendering-page-builder-items-on-infolist)
- [Rendering page builder ite previews on fomrms](#rendering-page-builder-ite-previews-on-fomrms)
- [Customizing actions and button rendering](#customizing-actions-and-button-rendering)
Expand Down Expand Up @@ -540,6 +541,133 @@ injections that are provided depends on where this function is used, if this is

one thing to note is that because `formatForListingView` uses `formatForSingleView` internally if you wish to inject something in `formatForSingleView` you will need to inject it in `formatForListingView` as well, otherwise it will not work.

### Global blocks

Global blocks are special blocks that have centralized configuration management. Instead of configuring the same block repeatedly across different pages, you can set up the block configuration once and reuse it everywhere.

#### Creating global blocks

You can create a global block using the make-block command with the `--global` flag:

```bash
php artisan page-builder-plugin:make-block ContactForm --type=view --global
```

This will:
- Create a global block class in `app/Filament/{panel}/Blocks/Globals/` directory
- Create a `Globals` block category for organization
- Show instructions for enabling the Global Blocks resource

#### Enabling the Global Blocks resource

To manage global blocks in your Filament admin panel, you need to register the GlobalBlocksPlugin in your panel provider:

```php
<?php

// In your AdminPanelProvider.php (or similar panel provider)

public function panel(Panel $panel): Panel
{
return $panel
// ... other configurations
->plugins([
\Redberry\PageBuilderPlugin\GlobalBlocksPlugin::make(),
// ... other plugins
]);
}
```

This will add a "Global Blocks" resource to your admin navigation under the "Content Management" group.

#### Configuring the Global Blocks resource

You can configure the Global Blocks resource behavior through the config file:

```php
<?php

// config/page-builder-plugin.php

return [
// ... other configurations

'global_blocks' => [
'enabled' => true, // Set to false to disable the resource
'resource' => \Redberry\PageBuilderPlugin\Resources\GlobalBlockConfigResource::class, // Custom resource class
],
];
```

To disable the Global Blocks resource completely, set `enabled` to `false`. To extend the resource with custom functionality, create your own resource class that extends the package's resource and specify it in the `resource` configuration.

#### How global blocks work

Global blocks use the `IsGlobalBlock` trait and have two key methods:

```php
<?php

use Redberry\PageBuilderPlugin\Traits\IsGlobalBlock;

class ContactForm extends BaseBlock
{
use IsGlobalBlock;

// Define the block's schema - this will be used in the Global Blocks resource
public static function getBaseBlockSchema(?object $record = null): array
{
return [
TextInput::make('title')->required(),
Textarea::make('description'),
TextInput::make('email')->email(),
];
}

// The schema returned to the page builder (empty for global blocks)
public static function getBlockSchema(?object $record = null): array
{
$schema = static::getBaseBlockSchema($record);
return static::applyGlobalConfiguration($schema);
}
}
```

#### Global Blocks resource

The "Global Blocks" resource is provided by the package and becomes available once you register the GlobalBlocksPlugin in your panel. This resource allows you to:

- View all available global blocks
- Configure each block's field values
- Edit configurations using the actual Filament form fields defined in `getBaseBlockSchema()`


#### Using global blocks on pages

When adding global blocks to pages:
- **No configuration modal appears** - blocks are added instantly
- **No per-page configuration** - all configuration is managed centrally
- **Consistent values everywhere** - the block uses the same configured values across all pages

Simply add your global block to the page builder's blocks array:

```php
<?php

PageBuilder::make('website_content')
->blocks([
ContactForm::class,
// other blocks...
]);
```

#### Key benefits

- **Centralized management**: Configure once, use everywhere
- **Consistency**: Same content and styling across all instances
- **Efficiency**: No need to reconfigure the same block on multiple pages
- **Maintainability**: Update global block configuration in one place

### Rendering page builder items on infolist
outside of form you might want to render page builder items on infolist, for this we provide two prebuilt entries:
`PageBuilderEntry` and `PageBuilderPreviewEntry`
Expand Down
5 changes: 5 additions & 0 deletions config/page-builder-plugin.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,9 @@
'block_model_class' => Redberry\PageBuilderPlugin\Models\PageBuilderBlock::class,

'polymorphic_relationship_name' => 'page_builder_blockable',

'global_blocks' => [
'enabled' => true,
'resource' => Redberry\PageBuilderPlugin\Resources\GlobalBlockConfigResource::class,
],
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

some apps have multiple panel providers, move this definitions to plugin registration step, so in case someone has multiple panels and want global blocks in only one of them they can configure it without lot of work

];
26 changes: 26 additions & 0 deletions database/migrations/create_global_block_configs_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()
{
Schema::create('global_block_configs', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('class_name')->unique();
$table->json('configuration')->nullable();
$table->timestamps();

$table->index('class_name');
});
}

public function down()
{
Schema::dropIfExists('global_block_configs');
}
};
102 changes: 83 additions & 19 deletions src/Commands/CreatePageBuilderPluginBlockCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ class CreatePageBuilderPluginBlockCommand extends Command
{
use CreatesClassFile;

public $signature = 'page-builder-plugin:make-block {name?} {--T|type=} {--panel=}';
public $signature = 'page-builder-plugin:make-block {name?} {--T|type=} {--panel=} {--global : Create a global block in Blocks/Globals directory}';

public $description = 'create a new block';

Expand All @@ -37,8 +37,16 @@ public function handle(): int

$this->panel = $this->getPanelToCreateIn();

$isGlobal = $this->option('global');

$blocksNamespace = $this->getClassNameSpaces('Blocks');

if ($isGlobal) {
$this->createGlobalsCategoryIfNotExists();
$isFirstGlobalBlock = $this->isFirstGlobalBlock();
$blocksNamespace .= '\\Globals';
}

$blockClass = $blocksNamespace . '\\' . $block;

try {
Expand All @@ -60,40 +68,96 @@ public function handle(): int
);

if ($blockType === 'iframe') {
$stubName = $isGlobal ? 'block.global' : 'block';
$replacements = [
'{{ class }}' => str($blockClass)->afterLast('\\')->replace('\\', ''),
'{{ namespace }}' => str($blockClass)->beforeLast('\\'),
];

if ($isGlobal) {
$replacements['{{ globalsNamespace }}'] = $this->getClassNameSpaces('BlockCategories');
}

$this->createFileFromStub(
'block',
$stubName,
$this->appClassToPath($blockClass),
[
'{{ class }}' => str($blockClass)->afterLast('\\')->replace('\\', ''),
'{{ namespace }}' => str($blockClass)->beforeLast('\\'),
]
$replacements
);
}

if ($blockType === 'view') {
$viewName = str($block)->replace('\\', '.')->kebab()->replace('.-', '.');
if ($isGlobal) {
$viewName = 'globals.' . $viewName;
}

$stubName = $isGlobal ? 'block.global.view' : 'block.view';
$replacements = [
'{{ class }}' => str($block)->afterLast('\\')->replace('\\', ''),
'{{ namespace }}' => str($blockClass)->beforeLast('\\'),
'{{ viewName }}' => $viewName,
'{{ panelId }}' => $this->panel->getId(),
];

if ($isGlobal) {
$replacements['{{ globalsNamespace }}'] = $this->getClassNameSpaces('BlockCategories');
}

$this->createFileFromStub(
'block.view',
$stubName,
$this->appClassToPath($blockClass),
[
'{{ class }}' => str($block)->afterLast('\\')->replace('\\', ''),
'{{ namespace }}' => str($blockClass)->beforeLast('\\'),
'{{ viewName }}' => $viewName,
'{{ panelId }}' => $this->panel->getId(),
]
$replacements
);

$viewPath = str($viewName)
->replace('.', '/')
->prepend("views/{$this->panel->getId()}/blocks/")
->append('.blade.php');

$this->createFileFromStub(
'block.blade',
resource_path(
$viewName
->replace('.', '/')
->prepend("views/{$this->panel->getId()}/blocks/")
->append('.blade.php')
),
resource_path($viewPath),
);
}

if ($isGlobal && isset($isFirstGlobalBlock) && $isFirstGlobalBlock) {
$this->info('To manage global blocks in Filament, add the GlobalBlocksPlugin to your panel:');
}

return self::SUCCESS;
}

protected function createGlobalsCategoryIfNotExists(): void
{
$categoryNamespace = $this->getClassNameSpaces('BlockCategories');
$globalsClass = $categoryNamespace . '\\Globals';

if (class_exists($globalsClass)) {
return;
}

$this->createFileFromStub(
'category-block',
$this->appClassToPath($globalsClass),
[
'{{ class }}' => 'Globals',
'{{ namespace }}' => $categoryNamespace,
]
);

$this->info("Created Globals category at: {$globalsClass}");
}

protected function isFirstGlobalBlock(): bool
{
$globalBlocksPath = app_path("Filament/{$this->panel->getId()}/Blocks/Globals");

if (! is_dir($globalBlocksPath)) {
return true;
}

$existingBlocks = glob($globalBlocksPath . '/*.php');

return empty($existingBlocks);
}
Comment on lines +151 to +162
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i do not think u will need this if u will remove those two function regarding exporting migrations and block resource

}
31 changes: 31 additions & 0 deletions src/Components/Forms/Actions/SelectBlockAction.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use Closure;
use Filament\Forms\Components\Actions\Action;
use Filament\Forms\Components\Select;
use Filament\Notifications\Notification;
use Filament\Support\Enums\MaxWidth;
use Illuminate\View\ComponentAttributeBag;
use Redberry\PageBuilderPlugin\Components\Forms\PageBuilder;
Expand Down Expand Up @@ -86,6 +87,36 @@ protected function setUp(): void
$this->stickyModalFooter();

$this->action(function ($data, $livewire, PageBuilder $component) {
$blockType = $data['block_type'];

$isGlobalBlock = class_exists($blockType) && method_exists($blockType, 'isGlobalBlock') && $blockType::isGlobalBlock();

if ($isGlobalBlock) {
$state = $component->getState() ?? [];

$block = app($component->getModel())->{$component->relationship}()->make([
'block_type' => $blockType,
'data' => [],
'order' => count($state) + 1,
]);

$block->id = $block->newUniqueId();

$component->state([
...$state,
$block->toArray(),
]);

$component->callAfterStateUpdated();

Notification::make()
->title('Block added successfully')
->success()
->send();

return;
}

$livewire->mountFormComponentAction(
$component->getStatePath(),
$component->getCreateActionName(),
Expand Down
Loading