Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
85 changes: 85 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,90 @@ 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
- Automatically generate a Global Blocks resource for centralized management (on first global block creation)
- Create the necessary database migration for storing global block configurations

#### 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

When you create your first global block, a "Global Blocks" resource is automatically generated in your Filament admin 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
163 changes: 144 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,157 @@ 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->createGlobalBlocksResource();
$this->publishGlobalBlockMigration();
}

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


protected function createGlobalBlocksResource(): void
{
$resourceNamespace = $this->getClassNameSpaces('Resources');
$resourceClass = 'GlobalBlocksResource';
$resourceFullClass = $resourceNamespace . '\\' . $resourceClass;

$this->createFileFromStub(
'global-blocks-resource',
$this->appClassToPath($resourceFullClass),
[
'{{ class }}' => $resourceClass,
'{{ namespace }}' => $resourceNamespace,
'{{ resourceNamespace }}' => $resourceNamespace,
]
);

$pagesNamespace = $resourceNamespace . '\\' . $resourceClass . '\\Pages';

$this->createFileFromStub(
'global-blocks-list-page',
$this->appClassToPath($pagesNamespace . '\\ListGlobalBlocks'),
[
'{{ class }}' => 'ListGlobalBlocks',
'{{ namespace }}' => $pagesNamespace,
'{{ resourceClass }}' => $resourceClass,
'{{ resourceNamespace }}' => $resourceNamespace,
]
);

$this->createFileFromStub(
'global-blocks-edit-page',
$this->appClassToPath($pagesNamespace . '\\EditGlobalBlock'),
[
'{{ class }}' => 'EditGlobalBlock',
'{{ namespace }}' => $pagesNamespace,
'{{ resourceClass }}' => $resourceClass,
'{{ resourceNamespace }}' => $resourceNamespace,
]
);

$this->info("Created Global Blocks resource at: {$resourceFullClass}");
$this->info('The resource has been automatically registered and will appear in your Filament panel navigation.');
}
Copy link
Member

Choose a reason for hiding this comment

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

regarding this block resource, its better if this resources will be directly on package side,
they can be registered in package provider, in case user will have the need to configure it they can just extend class located inside package.

make registration controllable from outside


protected function publishGlobalBlockMigration(): void
{
$timestamp = now()->format('Y_m_d_His');
$migrationName = 'create_global_block_configs_table';
$migrationFile = database_path("migrations/{$timestamp}_{$migrationName}.php");

$this->createFileFromStub(
'create_global_block_configs_table.php',
$migrationFile,
[]
);

$this->info("Created migration: {$migrationFile}");
$this->warn("Don't forget to run 'php artisan migrate' to create the global_block_configs table.");
}
Copy link
Member

Choose a reason for hiding this comment

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

no need to include this in a command, this can just be republished like other mgirations

}
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
Loading