Repository files navigation

Mason

A simple block-based drag and drop page / document builder field for Filament.

Latest VersionMIT LicensedTotal DownloadsGitHub Repo stars

Compatibility

Package VersionFilament Version
0.x3.x
1.x4.x
2.x5.x
3.x4.x, 5.x

Installation

You can install the package via composer:

composer require awcodes/mason

In an effort to align with Filament's theming methodology, you will need to use a custom theme to use this plugin.

Important

If you have not set up a custom theme and are using Filament Panels, follow the instructions in the Filament Docs first. The following applies to both the Panels Package and the standalone Forms package.

After setting up a custom theme, add the plugin's CSS to your theme CSS file or your app's CSS file if using the standalone forms package.

@import'../../../../vendor/awcodes/mason/resources/css/plugin.css';
@source'../../../../vendor/awcodes/mason/resources/**/*.blade.php';

Configuration

You can publish the config file with:

php artisan vendor:publish --tag="mason-config"

These are the contents of the published config file:

return [
'generator' => [
'namespace' => 'App\\Mason',
'views_path' => 'mason',
],
'preview' => [
'layout' => 'mason::iframe-preview',
],
'entry' => [
'layout' => 'mason::iframe-entry',
],
'routes' => [
'middleware' => ['web', 'auth'],
],
];

Auth Guards

By default, Mason uses the web and auth middleware for its routes internally. If you are using a different guard or have multiple guards, you can customize the middleware used by updating the routes.middleware configuration option in the published config file.

'routes' => [
'middleware' => ['web', 'auth:admin'],
],

Usage

Important

Mason uses JSON to store its data in the database, so it is important that you cast the field to either 'array' or 'json' on your model, and it's recommended to store the content as a longText column in the database.

Form Field

In your Filament forms you should use the Mason component. The Mason component accepts a name prop which should be the name of the field in your model, and takes an array of 'bricks' to make available to the editor. If you omit bricks, Mason falls back to its built-in Section brick.

useAwcodes\Mason\Mason;
useAwcodes\Mason\Bricks\Section;
->schema([
Mason::make('content')
->bricks([
Section::class,
]),
])

Field Preview Layout

Since Mason uses an iframe to render in the editor, you should set the preview layout for the field to a view in your application that includes your app's styles. This will ensure that the content in the editor looks similar to how it will look on the front end of your site. If all Mason fields in your forms use the same layout, you can set a default in the config file. Otherwise, you can set it per field like so:

Mason::make('content')
->previewLayout('layouts.mason-preview') // your app's layout
->bricks([...])

Then in your layout file you can include the necessary styles and includes to render the content correctly.

// resources/views/layouts/mason-preview.blade.php
<!DOCTYPE html>
<htmllang="{{str_replace('_', '-', app()->getLocale()) }}">
<head>
<metacharset="utf-8">
<metaname="viewport"content="width=device-width, initial-scale=1">
<title>{{config('app.name') }}</title>
<!-- Fonts -->
<linkrel="preconnect"href="https://fonts.bunny.net">
<linkhref="https://fonts.bunny.net/css?family=instrument-sans:400,500,600"rel="stylesheet" />
<!-- Styles / Scripts -->@vite(['resources/css/app.css', 'resources/js/app.js'])
<!-- Mason Styles -->@masonStyles </head>
<body>
<main>
<!-- Include Mason content rendering -->@include('mason::iframe-preview-content', ['blocks'=>$blocks])
</main>
</body>
</html>

If the blue color used in the editor doesn't work with your design, you can customize it with CSS in your app's CSS file.

#mason-preview-container {
--mason-border-color:rgb(236,72,153);
--mason-controls-background:rgba(0,0,0,0.8);
--mason-button-hover-background:rgba(255,255,255,0.2);
--mason-drop-zone-background:rgba(236,72,153,0.5);
}

Double-Clicking Bricks to Edit

By default, Mason requires you to click the edit button on each brick to edit its content. If you would like to enable double-clicking on bricks to open the edit modal, you can chain the doubleClickToEdit method on the field.

Mason::make('content')
->doubleClickToEdit()
->bricks([...])

Infolist Entry

In your Filament infolists you should use the MasonEntry component. The MasonEntry component accepts a name prop which should be the name of the field in your model, and takes an array of 'bricks' to make available to the entry. As with the field, omitting bricks falls back to the built-in Section brick.

useAwcodes\Mason\MasonEntry;
useAwcodes\Mason\Bricks\Section;
->schema([
MasonEntry::make('content')
->bricks([
Section::class,
]),
])

Entry Preview Layout

Since Mason uses an iframe to render in the infolist, you should set the preview layout for the field to a view in your application that includes your app's styles. This will ensure that the content in the editor looks similar to how it will look on the front end of your site. If all Mason fields in your forms use the same layout, you can set a default in the config file. Otherwise, you can set it per field like so:

MasonEntry::make('content')
->previewLayout('layouts.mason-entry')
->bricks([...])

Then in your layout file you can include the necessary styles and includes to render the content correctly.

// resources/views/layouts/mason-entry.blade.php
<!DOCTYPE html>
<htmllang="{{str_replace('_', '-', app()->getLocale()) }}">
<head>
<metacharset="utf-8">
<metaname="viewport"content="width=device-width, initial-scale=1">
<title>{{config('app.name') }}</title>
<!-- Fonts -->
<linkrel="preconnect"href="https://fonts.bunny.net">
<linkhref="https://fonts.bunny.net/css?family=instrument-sans:400,500,600"rel="stylesheet" />
<!-- Styles / Scripts -->@vite(['resources/css/app.css', 'resources/js/app.js'])
<!-- Mason Entry Styles -->@masonEntryStyles
</head>
<body>
<main>
<!-- Include MasonEntry content rendering -->@include('mason::iframe-entry-content', ['blocks'=>$blocks])
</main>
</body>
</html>

Tips & Tricks

Custom Height

If you find that the default height of the Mason editor or entry is not enough for your use case, you can customize using Filament's default ->extraInputAttrbutes() method on both the Mason field and the MasonEntry component.

Mason::make('content')
->extraInputAttributes(['style' => 'min-height: 30rem;'])
->bricks([...])
MasonEntry::make('content')
->extraInputAttributes(['style' => 'min-height: 40rem;'])
->bricks([...])

Brick Collections

To keep from having to repeat yourself when assigning bricks to the editor and the entry, it would help to create sets of bricks that make sense for their use case. Then you can use that in the bricks method.

class BrickCollection
{
publicstaticfunctionmake(): array
{
return [
NewsletterSignup::class,
Section::class,
Cards::class,
SupportCenter::class,
];
}
}
Mason::make('content')
->bricks(BrickCollection::make())
MasonEntry::make('content')
->bricks(BrickCollection::make())

Sidebar Position

By default, the Mason editor sidebar is positioned on the right side of the editor. If you would like to position it on the left side, you can chain the sidebarPosition method on the field and assign it the new position.

useAwcodes\Mason\Enums\SidebarPosition;
Mason::make('content')
->sidebarPosition(SidebarPosition::Start)
->bricks([...])

Displaying Brick Actions as a Grid

By default, the Mason editor displays the brick actions in a list. If you would like to display them in a grid format, you can chain the displayActionsAsGrid method on the field.

Mason::make('content')
->displayActionsAsGrid()
->bricks([...])

Grouping Bricks

Bricks can be organized into labeled groups in the editor sidebar by wrapping them in a BrickGroup. Groups are collapsible, and searching by brick name or tag will automatically expand any group that contains a match.

useAwcodes\Mason\BrickGroup;
Mason::make('content')
->bricks([
BrickGroup::make('Content')
->bricks([
Section::class,
Grid::class,
]),
BrickGroup::make('Marketing')
->bricks([
Hero::class,
CallToAction::class,
]),
LeadForm::class, // standalone bricks can sit alongside groups
])

Groups and standalone bricks can be freely mixed in the same bricks array. sortBricks() applies to the top-level array and will sort groups alongside standalone bricks by their respective labels.

Sorting Bricks

By default, bricks are sorted in the order they are defined in the bricks array. If you would like to allow users to sort the bricks in the editor, you can chain the sortBricks method on the field.

// Sort ascending (A-Z) by label
Mason::make('content')
->sortBricks('asc')
->bricks([...])
// Or simply (defaults to 'asc')
Mason::make('content')
->sortBricks()
->bricks([...])
// Sort descending (Z-A) by label
Mason::make('content')
->sortBricks('desc')
->bricks([...])

Static Bricks without data or forms

If you would like to create a brick that does not require any data or forms, you can return the view in the toHtml method and set the action to have a hidden modal in the configureBrickAction method in your brick class. Now, when inserting the brick, it will simply add the brick without any configuration.

publicstaticfunctiontoHtml(array$config, ?array$data = null): ?string
{
returnview('mason.static-brick');
}
publicstaticfunctionconfigureBrickAction(Action$action): Action
{
return$action->modalHidden();
}

Light / Dark Mode

If your application supports light and dark mode, you can optionally add support for it in the Mason editor by using the colorModeToggle method on the field. This will add a toggle button to the editor sidebar that allows users to switch between light and dark mode. You can also use the defaultColorMode method to set the default color mode for the editor. One thing to note is that defaultColorMode only sets the initial mode when the editor is loaded. If the user switches modes, their preference will be saved in local storage for future visits.

In order for this to work properly, you will need to ensure that your application's CSS supports manually setting light and dark mode according to the Tailwind CSS documentation on manually controlling color mode.

@custom-variant dark (&:where(.dark, .dark*));
Mason::make('content')
->colorModeToggle()
->defaultColorMode('dark')
->bricks([...])

Creating Bricks

Bricks are nothing more than classes that have an associated view that is rendered in the editor with its data.

To help you get started, there is a make:mason-brick command that will create a new brick for you with the necessary class and blade template in the paths specified in the config file.

php artisan make:mason-brick Section

This will create a new brick in the App\Mason namespace with the class Section and a preview and index blade template in the resources/views/mason directory. Bricks follow the same conventions as Filament RichEditor custom blocks.

useAwcodes\Mason\Brick;
useFilament\Actions\Action;
useFilament\Forms\Components\FileUpload;
useFilament\Forms\Components\Radio;
useFilament\Forms\Components\RichEditor;
useFilament\Forms\Components\ToggleButtons;
useFilament\Schemas\Components\Grid;
useFilament\Schemas\Components\SectionasFilamentSection;
useFilament\Support\Icons\Heroicon;
useIlluminate\Contracts\Support\Htmlable;
useIlluminate\Support\HtmlString;
useThrowable;
class Section extends Brick
{
publicstaticfunctiongetId(): string
{
return'section';
}
publicstaticfunctiongetLabel(): string
{
returnparent::getLabel();
}
publicstaticfunctiongetIcon(): string | Heroicon | Htmlable | null
{
returnnewHtmlString('<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20 20h.01M4 20h.01M8 20h.01M12 20h.01M16 20h.01M20 4h.01M4 4h.01M8 4h.01M12 4h.01M16 4v.01M4 9a1 1 0 0 1 1-1h14a1 1 0 0 1 1 1v6a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1z"/></svg>');
}
publicstaticfunctiongetTags(): array
{
return ['section', 'content', 'layout'];
}
/** * @throws Throwable */publicstaticfunctiontoHtml(array$config, ?array$data = null): ?string
{
returnview('mason::bricks.section.index', [
'background_color' => $config['background_color'] ?? 'white',
'image' => $config['image'] ?? null,
'text' => $config['text'] ?? null,
])->render();
}
publicstaticfunctionconfigureBrickAction(Action$action): Action
{
return$action
->slideOver()
->schema([
Radio::make('background_color'),
FileUpload::make('image'),
RichEditor::make('text'),
]);
}
}

Brick Tags

Bricks can optionally declare tags to improve discoverability when searching the editor sidebar. When a user types in the search box, Mason will match against both the brick's label and any of its tags, so a search for "marketing" can surface a brick whose label is "Hero" as long as that tag is defined.

publicstaticfunctiongetTags(): array
{
return ['hero', 'banner', 'header', 'landing page', 'marketing'];
}

By default, getTags() returns an empty array, so tags are entirely optional.

Rendering Content

You are free to render the content however you see fit. The data is stored in the database as JSON, so you can use the data however you see fit. But the plugin offers a helper method for converting the data to HTML should you choose to use it.

Similar to the form field and entry components, the helper needs to know what bricks are available. You can pass the bricks to the helper as the second argument. See, above about creating a collection of bricks. This will help keep your code DRY.

{!! mason(content: $post->content, bricks: \App\Mason\BrickCollection::make())->toHtml() !!}

There is also a dedicated Render that can be used if you need more control over the rendering process.

useAwcodes\Mason\Support\MasonRenderer;
$renderer = MasonRenderer::make($content)->bricks(\App\Mason\BrickCollection::make());
$renderer->toHtml()
$renderer->toUnsafeHtml();
$renderer->toArray();
$renderer->toText();

Faking Content

When testing, you may want to fake some Mason content. There is a helper method for that as well.

useAwcodes\Mason\Support\Faker;
Faker::make()
->brick(
id: 'section',
config: [
'background_color' => 'white',
'text' => '<h2>This is a heading</h2><p>Just some random text for a paragraph</p>',
'image' => null,
]
)
->brick(
id: 'section',
config: [
'background_color' => 'primary',
'text' => '<h2>This is a heading</h2><p>Just some random text for a paragraph</p>',
'image' => null,
]
)
->asJson(),

Testing

composer test

Development

Mason ships a Workbench — a small Laravel application, powered by Orchestra Testbench, that consumes the package the way a real application would. There is no need for a separate Laravel project.

composer install
composer serve

Then open the panel at /admin and the frontend at /. The login form is prefilled with the seeded development account:

Email: test@example.com
Password: password

See CONTRIBUTING for the rest.

Contributing

Please see CONTRIBUTING for details.

Security Vulnerabilities

Please review our security policy on how to report security vulnerabilities.

Credits

License

The MIT License (MIT). Please see License File for more information.

About

A simple block based drag and drop page / document builder field for Filament.

Topics

Resources

Contributing

Security policy

Stars

246 stars

Watchers

3 watching

Forks

Releases

Sponsor this project

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Repository files navigation

Mason

A simple block-based drag and drop page / document builder field for Filament.

Latest VersionMIT LicensedTotal DownloadsGitHub Repo stars

Compatibility

Package VersionFilament Version
0.x3.x
1.x4.x
2.x5.x
3.x4.x, 5.x

Installation

You can install the package via composer:

composer require awcodes/mason

In an effort to align with Filament's theming methodology, you will need to use a custom theme to use this plugin.

Important

If you have not set up a custom theme and are using Filament Panels, follow the instructions in the Filament Docs first. The following applies to both the Panels Package and the standalone Forms package.

After setting up a custom theme, add the plugin's CSS to your theme CSS file or your app's CSS file if using the standalone forms package.

@import'../../../../vendor/awcodes/mason/resources/css/plugin.css';
@source'../../../../vendor/awcodes/mason/resources/**/*.blade.php';

Configuration

You can publish the config file with:

php artisan vendor:publish --tag="mason-config"

These are the contents of the published config file:

return [
'generator' => [
'namespace' => 'App\\Mason',
'views_path' => 'mason',
],
'preview' => [
'layout' => 'mason::iframe-preview',
],
'entry' => [
'layout' => 'mason::iframe-entry',
],
'routes' => [
'middleware' => ['web', 'auth'],
],
];

Auth Guards

By default, Mason uses the web and auth middleware for its routes internally. If you are using a different guard or have multiple guards, you can customize the middleware used by updating the routes.middleware configuration option in the published config file.

'routes' => [
'middleware' => ['web', 'auth:admin'],
],

Usage

Important

Mason uses JSON to store its data in the database, so it is important that you cast the field to either 'array' or 'json' on your model, and it's recommended to store the content as a longText column in the database.

Form Field

In your Filament forms you should use the Mason component. The Mason component accepts a name prop which should be the name of the field in your model, and takes an array of 'bricks' to make available to the editor. If you omit bricks, Mason falls back to its built-in Section brick.

useAwcodes\Mason\Mason;
useAwcodes\Mason\Bricks\Section;
->schema([
Mason::make('content')
->bricks([
Section::class,
]),
])

Field Preview Layout

Since Mason uses an iframe to render in the editor, you should set the preview layout for the field to a view in your application that includes your app's styles. This will ensure that the content in the editor looks similar to how it will look on the front end of your site. If all Mason fields in your forms use the same layout, you can set a default in the config file. Otherwise, you can set it per field like so:

Mason::make('content')
->previewLayout('layouts.mason-preview') // your app's layout
->bricks([...])

Then in your layout file you can include the necessary styles and includes to render the content correctly.

// resources/views/layouts/mason-preview.blade.php
<!DOCTYPE html>
<htmllang="{{str_replace('_', '-', app()->getLocale()) }}">
<head>
<metacharset="utf-8">
<metaname="viewport"content="width=device-width, initial-scale=1">
<title>{{config('app.name') }}</title>
<!-- Fonts -->
<linkrel="preconnect"href="https://fonts.bunny.net">
<linkhref="https://fonts.bunny.net/css?family=instrument-sans:400,500,600"rel="stylesheet" />
<!-- Styles / Scripts -->@vite(['resources/css/app.css', 'resources/js/app.js'])
<!-- Mason Styles -->@masonStyles </head>
<body>
<main>
<!-- Include Mason content rendering -->@include('mason::iframe-preview-content', ['blocks'=>$blocks])
</main>
</body>
</html>

If the blue color used in the editor doesn't work with your design, you can customize it with CSS in your app's CSS file.

#mason-preview-container {
--mason-border-color:rgb(236,72,153);
--mason-controls-background:rgba(0,0,0,0.8);
--mason-button-hover-background:rgba(255,255,255,0.2);
--mason-drop-zone-background:rgba(236,72,153,0.5);
}

Double-Clicking Bricks to Edit

By default, Mason requires you to click the edit button on each brick to edit its content. If you would like to enable double-clicking on bricks to open the edit modal, you can chain the doubleClickToEdit method on the field.

Mason::make('content')
->doubleClickToEdit()
->bricks([...])

Infolist Entry

In your Filament infolists you should use the MasonEntry component. The MasonEntry component accepts a name prop which should be the name of the field in your model, and takes an array of 'bricks' to make available to the entry. As with the field, omitting bricks falls back to the built-in Section brick.

useAwcodes\Mason\MasonEntry;
useAwcodes\Mason\Bricks\Section;
->schema([
MasonEntry::make('content')
->bricks([
Section::class,
]),
])

Entry Preview Layout

Since Mason uses an iframe to render in the infolist, you should set the preview layout for the field to a view in your application that includes your app's styles. This will ensure that the content in the editor looks similar to how it will look on the front end of your site. If all Mason fields in your forms use the same layout, you can set a default in the config file. Otherwise, you can set it per field like so:

MasonEntry::make('content')
->previewLayout('layouts.mason-entry')
->bricks([...])

Then in your layout file you can include the necessary styles and includes to render the content correctly.

// resources/views/layouts/mason-entry.blade.php
<!DOCTYPE html>
<htmllang="{{str_replace('_', '-', app()->getLocale()) }}">
<head>
<metacharset="utf-8">
<metaname="viewport"content="width=device-width, initial-scale=1">
<title>{{config('app.name') }}</title>
<!-- Fonts -->
<linkrel="preconnect"href="https://fonts.bunny.net">
<linkhref="https://fonts.bunny.net/css?family=instrument-sans:400,500,600"rel="stylesheet" />
<!-- Styles / Scripts -->@vite(['resources/css/app.css', 'resources/js/app.js'])
<!-- Mason Entry Styles -->@masonEntryStyles
</head>
<body>
<main>
<!-- Include MasonEntry content rendering -->@include('mason::iframe-entry-content', ['blocks'=>$blocks])
</main>
</body>
</html>

Tips & Tricks

Custom Height

If you find that the default height of the Mason editor or entry is not enough for your use case, you can customize using Filament's default ->extraInputAttrbutes() method on both the Mason field and the MasonEntry component.

Mason::make('content')
->extraInputAttributes(['style' => 'min-height: 30rem;'])
->bricks([...])
MasonEntry::make('content')
->extraInputAttributes(['style' => 'min-height: 40rem;'])
->bricks([...])

Brick Collections

To keep from having to repeat yourself when assigning bricks to the editor and the entry, it would help to create sets of bricks that make sense for their use case. Then you can use that in the bricks method.

class BrickCollection
{
publicstaticfunctionmake(): array
{
return [
NewsletterSignup::class,
Section::class,
Cards::class,
SupportCenter::class,
];
}
}
Mason::make('content')
->bricks(BrickCollection::make())
MasonEntry::make('content')
->bricks(BrickCollection::make())

Sidebar Position

By default, the Mason editor sidebar is positioned on the right side of the editor. If you would like to position it on the left side, you can chain the sidebarPosition method on the field and assign it the new position.

useAwcodes\Mason\Enums\SidebarPosition;
Mason::make('content')
->sidebarPosition(SidebarPosition::Start)
->bricks([...])

Displaying Brick Actions as a Grid

By default, the Mason editor displays the brick actions in a list. If you would like to display them in a grid format, you can chain the displayActionsAsGrid method on the field.

Mason::make('content')
->displayActionsAsGrid()
->bricks([...])

Grouping Bricks

Bricks can be organized into labeled groups in the editor sidebar by wrapping them in a BrickGroup. Groups are collapsible, and searching by brick name or tag will automatically expand any group that contains a match.

useAwcodes\Mason\BrickGroup;
Mason::make('content')
->bricks([
BrickGroup::make('Content')
->bricks([
Section::class,
Grid::class,
]),
BrickGroup::make('Marketing')
->bricks([
Hero::class,
CallToAction::class,
]),
LeadForm::class, // standalone bricks can sit alongside groups
])

Groups and standalone bricks can be freely mixed in the same bricks array. sortBricks() applies to the top-level array and will sort groups alongside standalone bricks by their respective labels.

Sorting Bricks

By default, bricks are sorted in the order they are defined in the bricks array. If you would like to allow users to sort the bricks in the editor, you can chain the sortBricks method on the field.

// Sort ascending (A-Z) by label
Mason::make('content')
->sortBricks('asc')
->bricks([...])
// Or simply (defaults to 'asc')
Mason::make('content')
->sortBricks()
->bricks([...])
// Sort descending (Z-A) by label
Mason::make('content')
->sortBricks('desc')
->bricks([...])

Static Bricks without data or forms

If you would like to create a brick that does not require any data or forms, you can return the view in the toHtml method and set the action to have a hidden modal in the configureBrickAction method in your brick class. Now, when inserting the brick, it will simply add the brick without any configuration.

publicstaticfunctiontoHtml(array$config, ?array$data = null): ?string
{
returnview('mason.static-brick');
}
publicstaticfunctionconfigureBrickAction(Action$action): Action
{
return$action->modalHidden();
}

Light / Dark Mode

If your application supports light and dark mode, you can optionally add support for it in the Mason editor by using the colorModeToggle method on the field. This will add a toggle button to the editor sidebar that allows users to switch between light and dark mode. You can also use the defaultColorMode method to set the default color mode for the editor. One thing to note is that defaultColorMode only sets the initial mode when the editor is loaded. If the user switches modes, their preference will be saved in local storage for future visits.

In order for this to work properly, you will need to ensure that your application's CSS supports manually setting light and dark mode according to the Tailwind CSS documentation on manually controlling color mode.

@custom-variant dark (&:where(.dark, .dark*));
Mason::make('content')
->colorModeToggle()
->defaultColorMode('dark')
->bricks([...])

Creating Bricks

Bricks are nothing more than classes that have an associated view that is rendered in the editor with its data.

To help you get started, there is a make:mason-brick command that will create a new brick for you with the necessary class and blade template in the paths specified in the config file.

php artisan make:mason-brick Section

This will create a new brick in the App\Mason namespace with the class Section and a preview and index blade template in the resources/views/mason directory. Bricks follow the same conventions as Filament RichEditor custom blocks.

useAwcodes\Mason\Brick;
useFilament\Actions\Action;
useFilament\Forms\Components\FileUpload;
useFilament\Forms\Components\Radio;
useFilament\Forms\Components\RichEditor;
useFilament\Forms\Components\ToggleButtons;
useFilament\Schemas\Components\Grid;
useFilament\Schemas\Components\SectionasFilamentSection;
useFilament\Support\Icons\Heroicon;
useIlluminate\Contracts\Support\Htmlable;
useIlluminate\Support\HtmlString;
useThrowable;
class Section extends Brick
{
publicstaticfunctiongetId(): string
{
return'section';
}
publicstaticfunctiongetLabel(): string
{
returnparent::getLabel();
}
publicstaticfunctiongetIcon(): string | Heroicon | Htmlable | null
{
returnnewHtmlString('<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20 20h.01M4 20h.01M8 20h.01M12 20h.01M16 20h.01M20 4h.01M4 4h.01M8 4h.01M12 4h.01M16 4v.01M4 9a1 1 0 0 1 1-1h14a1 1 0 0 1 1 1v6a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1z"/></svg>');
}
publicstaticfunctiongetTags(): array
{
return ['section', 'content', 'layout'];
}
/** * @throws Throwable */publicstaticfunctiontoHtml(array$config, ?array$data = null): ?string
{
returnview('mason::bricks.section.index', [
'background_color' => $config['background_color'] ?? 'white',
'image' => $config['image'] ?? null,
'text' => $config['text'] ?? null,
])->render();
}
publicstaticfunctionconfigureBrickAction(Action$action): Action
{
return$action
->slideOver()
->schema([
Radio::make('background_color'),
FileUpload::make('image'),
RichEditor::make('text'),
]);
}
}

Brick Tags

Bricks can optionally declare tags to improve discoverability when searching the editor sidebar. When a user types in the search box, Mason will match against both the brick's label and any of its tags, so a search for "marketing" can surface a brick whose label is "Hero" as long as that tag is defined.

publicstaticfunctiongetTags(): array
{
return ['hero', 'banner', 'header', 'landing page', 'marketing'];
}

By default, getTags() returns an empty array, so tags are entirely optional.

Rendering Content

You are free to render the content however you see fit. The data is stored in the database as JSON, so you can use the data however you see fit. But the plugin offers a helper method for converting the data to HTML should you choose to use it.

Similar to the form field and entry components, the helper needs to know what bricks are available. You can pass the bricks to the helper as the second argument. See, above about creating a collection of bricks. This will help keep your code DRY.

{!! mason(content: $post->content, bricks: \App\Mason\BrickCollection::make())->toHtml() !!}

There is also a dedicated Render that can be used if you need more control over the rendering process.

useAwcodes\Mason\Support\MasonRenderer;
$renderer = MasonRenderer::make($content)->bricks(\App\Mason\BrickCollection::make());
$renderer->toHtml()
$renderer->toUnsafeHtml();
$renderer->toArray();
$renderer->toText();

Faking Content

When testing, you may want to fake some Mason content. There is a helper method for that as well.

useAwcodes\Mason\Support\Faker;
Faker::make()
->brick(
id: 'section',
config: [
'background_color' => 'white',
'text' => '<h2>This is a heading</h2><p>Just some random text for a paragraph</p>',
'image' => null,
]
)
->brick(
id: 'section',
config: [
'background_color' => 'primary',
'text' => '<h2>This is a heading</h2><p>Just some random text for a paragraph</p>',
'image' => null,
]
)
->asJson(),

Testing

composer test

Development

Mason ships a Workbench — a small Laravel application, powered by Orchestra Testbench, that consumes the package the way a real application would. There is no need for a separate Laravel project.

composer install
composer serve

Then open the panel at /admin and the frontend at /. The login form is prefilled with the seeded development account:

Email: test@example.com
Password: password

See CONTRIBUTING for the rest.

Contributing

Please see CONTRIBUTING for details.

Security Vulnerabilities

Please review our security policy on how to report security vulnerabilities.

Credits

License

The MIT License (MIT). Please see License File for more information.

About

A simple block based drag and drop page / document builder field for Filament.

Topics

Resources

Contributing

Security policy

Stars

246 stars

Watchers

3 watching

Forks

Releases

Sponsor this project

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

Mason

A simple block-based drag and drop page / document builder field for Filament.

Latest VersionMIT LicensedTotal DownloadsGitHub Repo stars

Compatibility

Package VersionFilament Version
0.x3.x
1.x4.x
2.x5.x
3.x4.x, 5.x

Installation

You can install the package via composer:

composer require awcodes/mason

In an effort to align with Filament's theming methodology, you will need to use a custom theme to use this plugin.

Important

If you have not set up a custom theme and are using Filament Panels, follow the instructions in the Filament Docs first. The following applies to both the Panels Package and the standalone Forms package.

After setting up a custom theme, add the plugin's CSS to your theme CSS file or your app's CSS file if using the standalone forms package.

@import'../../../../vendor/awcodes/mason/resources/css/plugin.css';
@source'../../../../vendor/awcodes/mason/resources/**/*.blade.php';

Configuration

You can publish the config file with:

php artisan vendor:publish --tag="mason-config"

These are the contents of the published config file:

return [
'generator' => [
'namespace' => 'App\\Mason',
'views_path' => 'mason',
],
'preview' => [
'layout' => 'mason::iframe-preview',
],
'entry' => [
'layout' => 'mason::iframe-entry',
],
'routes' => [
'middleware' => ['web', 'auth'],
],
];

Auth Guards

By default, Mason uses the web and auth middleware for its routes internally. If you are using a different guard or have multiple guards, you can customize the middleware used by updating the routes.middleware configuration option in the published config file.

'routes' => [
'middleware' => ['web', 'auth:admin'],
],

Usage

Important

Mason uses JSON to store its data in the database, so it is important that you cast the field to either 'array' or 'json' on your model, and it's recommended to store the content as a longText column in the database.

Form Field

In your Filament forms you should use the Mason component. The Mason component accepts a name prop which should be the name of the field in your model, and takes an array of 'bricks' to make available to the editor. If you omit bricks, Mason falls back to its built-in Section brick.

useAwcodes\Mason\Mason;
useAwcodes\Mason\Bricks\Section;
->schema([
Mason::make('content')
->bricks([
Section::class,
]),
])

Field Preview Layout

Since Mason uses an iframe to render in the editor, you should set the preview layout for the field to a view in your application that includes your app's styles. This will ensure that the content in the editor looks similar to how it will look on the front end of your site. If all Mason fields in your forms use the same layout, you can set a default in the config file. Otherwise, you can set it per field like so:

Mason::make('content')
->previewLayout('layouts.mason-preview') // your app's layout
->bricks([...])

Then in your layout file you can include the necessary styles and includes to render the content correctly.

// resources/views/layouts/mason-preview.blade.php
<!DOCTYPE html>
<htmllang="{{str_replace('_', '-', app()->getLocale()) }}">
<head>
<metacharset="utf-8">
<metaname="viewport"content="width=device-width, initial-scale=1">
<title>{{config('app.name') }}</title>
<!-- Fonts -->
<linkrel="preconnect"href="https://fonts.bunny.net">
<linkhref="https://fonts.bunny.net/css?family=instrument-sans:400,500,600"rel="stylesheet" />
<!-- Styles / Scripts -->@vite(['resources/css/app.css', 'resources/js/app.js'])
<!-- Mason Styles -->@masonStyles </head>
<body>
<main>
<!-- Include Mason content rendering -->@include('mason::iframe-preview-content', ['blocks'=>$blocks])
</main>
</body>
</html>

If the blue color used in the editor doesn't work with your design, you can customize it with CSS in your app's CSS file.

#mason-preview-container {
--mason-border-color:rgb(236,72,153);
--mason-controls-background:rgba(0,0,0,0.8);
--mason-button-hover-background:rgba(255,255,255,0.2);
--mason-drop-zone-background:rgba(236,72,153,0.5);
}

Double-Clicking Bricks to Edit

By default, Mason requires you to click the edit button on each brick to edit its content. If you would like to enable double-clicking on bricks to open the edit modal, you can chain the doubleClickToEdit method on the field.

Mason::make('content')
->doubleClickToEdit()
->bricks([...])

Infolist Entry

In your Filament infolists you should use the MasonEntry component. The MasonEntry component accepts a name prop which should be the name of the field in your model, and takes an array of 'bricks' to make available to the entry. As with the field, omitting bricks falls back to the built-in Section brick.

useAwcodes\Mason\MasonEntry;
useAwcodes\Mason\Bricks\Section;
->schema([
MasonEntry::make('content')
->bricks([
Section::class,
]),
])

Entry Preview Layout

Since Mason uses an iframe to render in the infolist, you should set the preview layout for the field to a view in your application that includes your app's styles. This will ensure that the content in the editor looks similar to how it will look on the front end of your site. If all Mason fields in your forms use the same layout, you can set a default in the config file. Otherwise, you can set it per field like so:

MasonEntry::make('content')
->previewLayout('layouts.mason-entry')
->bricks([...])

Then in your layout file you can include the necessary styles and includes to render the content correctly.

// resources/views/layouts/mason-entry.blade.php
<!DOCTYPE html>
<htmllang="{{str_replace('_', '-', app()->getLocale()) }}">
<head>
<metacharset="utf-8">
<metaname="viewport"content="width=device-width, initial-scale=1">
<title>{{config('app.name') }}</title>
<!-- Fonts -->
<linkrel="preconnect"href="https://fonts.bunny.net">
<linkhref="https://fonts.bunny.net/css?family=instrument-sans:400,500,600"rel="stylesheet" />
<!-- Styles / Scripts -->@vite(['resources/css/app.css', 'resources/js/app.js'])
<!-- Mason Entry Styles -->@masonEntryStyles
</head>
<body>
<main>
<!-- Include MasonEntry content rendering -->@include('mason::iframe-entry-content', ['blocks'=>$blocks])
</main>
</body>
</html>

Tips & Tricks

Custom Height

If you find that the default height of the Mason editor or entry is not enough for your use case, you can customize using Filament's default ->extraInputAttrbutes() method on both the Mason field and the MasonEntry component.

Mason::make('content')
->extraInputAttributes(['style' => 'min-height: 30rem;'])
->bricks([...])
MasonEntry::make('content')
->extraInputAttributes(['style' => 'min-height: 40rem;'])
->bricks([...])

Brick Collections

To keep from having to repeat yourself when assigning bricks to the editor and the entry, it would help to create sets of bricks that make sense for their use case. Then you can use that in the bricks method.

class BrickCollection
{
publicstaticfunctionmake(): array
{
return [
NewsletterSignup::class,
Section::class,
Cards::class,
SupportCenter::class,
];
}
}
Mason::make('content')
->bricks(BrickCollection::make())
MasonEntry::make('content')
->bricks(BrickCollection::make())

Sidebar Position

By default, the Mason editor sidebar is positioned on the right side of the editor. If you would like to position it on the left side, you can chain the sidebarPosition method on the field and assign it the new position.

useAwcodes\Mason\Enums\SidebarPosition;
Mason::make('content')
->sidebarPosition(SidebarPosition::Start)
->bricks([...])

Displaying Brick Actions as a Grid

By default, the Mason editor displays the brick actions in a list. If you would like to display them in a grid format, you can chain the displayActionsAsGrid method on the field.

Mason::make('content')
->displayActionsAsGrid()
->bricks([...])

Grouping Bricks

Bricks can be organized into labeled groups in the editor sidebar by wrapping them in a BrickGroup. Groups are collapsible, and searching by brick name or tag will automatically expand any group that contains a match.

useAwcodes\Mason\BrickGroup;
Mason::make('content')
->bricks([
BrickGroup::make('Content')
->bricks([
Section::class,
Grid::class,
]),
BrickGroup::make('Marketing')
->bricks([
Hero::class,
CallToAction::class,
]),
LeadForm::class, // standalone bricks can sit alongside groups
])

Groups and standalone bricks can be freely mixed in the same bricks array. sortBricks() applies to the top-level array and will sort groups alongside standalone bricks by their respective labels.

Sorting Bricks

By default, bricks are sorted in the order they are defined in the bricks array. If you would like to allow users to sort the bricks in the editor, you can chain the sortBricks method on the field.

// Sort ascending (A-Z) by label
Mason::make('content')
->sortBricks('asc')
->bricks([...])
// Or simply (defaults to 'asc')
Mason::make('content')
->sortBricks()
->bricks([...])
// Sort descending (Z-A) by label
Mason::make('content')
->sortBricks('desc')
->bricks([...])

Static Bricks without data or forms

If you would like to create a brick that does not require any data or forms, you can return the view in the toHtml method and set the action to have a hidden modal in the configureBrickAction method in your brick class. Now, when inserting the brick, it will simply add the brick without any configuration.

publicstaticfunctiontoHtml(array$config, ?array$data = null): ?string
{
returnview('mason.static-brick');
}
publicstaticfunctionconfigureBrickAction(Action$action): Action
{
return$action->modalHidden();
}

Light / Dark Mode

If your application supports light and dark mode, you can optionally add support for it in the Mason editor by using the colorModeToggle method on the field. This will add a toggle button to the editor sidebar that allows users to switch between light and dark mode. You can also use the defaultColorMode method to set the default color mode for the editor. One thing to note is that defaultColorMode only sets the initial mode when the editor is loaded. If the user switches modes, their preference will be saved in local storage for future visits.

In order for this to work properly, you will need to ensure that your application's CSS supports manually setting light and dark mode according to the Tailwind CSS documentation on manually controlling color mode.

@custom-variant dark (&:where(.dark, .dark*));
Mason::make('content')
->colorModeToggle()
->defaultColorMode('dark')
->bricks([...])

Creating Bricks

Bricks are nothing more than classes that have an associated view that is rendered in the editor with its data.

To help you get started, there is a make:mason-brick command that will create a new brick for you with the necessary class and blade template in the paths specified in the config file.

php artisan make:mason-brick Section

This will create a new brick in the App\Mason namespace with the class Section and a preview and index blade template in the resources/views/mason directory. Bricks follow the same conventions as Filament RichEditor custom blocks.

useAwcodes\Mason\Brick;
useFilament\Actions\Action;
useFilament\Forms\Components\FileUpload;
useFilament\Forms\Components\Radio;
useFilament\Forms\Components\RichEditor;
useFilament\Forms\Components\ToggleButtons;
useFilament\Schemas\Components\Grid;
useFilament\Schemas\Components\SectionasFilamentSection;
useFilament\Support\Icons\Heroicon;
useIlluminate\Contracts\Support\Htmlable;
useIlluminate\Support\HtmlString;
useThrowable;
class Section extends Brick
{
publicstaticfunctiongetId(): string
{
return'section';
}
publicstaticfunctiongetLabel(): string
{
returnparent::getLabel();
}
publicstaticfunctiongetIcon(): string | Heroicon | Htmlable | null
{
returnnewHtmlString('<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20 20h.01M4 20h.01M8 20h.01M12 20h.01M16 20h.01M20 4h.01M4 4h.01M8 4h.01M12 4h.01M16 4v.01M4 9a1 1 0 0 1 1-1h14a1 1 0 0 1 1 1v6a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1z"/></svg>');
}
publicstaticfunctiongetTags(): array
{
return ['section', 'content', 'layout'];
}
/** * @throws Throwable */publicstaticfunctiontoHtml(array$config, ?array$data = null): ?string
{
returnview('mason::bricks.section.index', [
'background_color' => $config['background_color'] ?? 'white',
'image' => $config['image'] ?? null,
'text' => $config['text'] ?? null,
])->render();
}
publicstaticfunctionconfigureBrickAction(Action$action): Action
{
return$action
->slideOver()
->schema([
Radio::make('background_color'),
FileUpload::make('image'),
RichEditor::make('text'),
]);
}
}

Brick Tags

Bricks can optionally declare tags to improve discoverability when searching the editor sidebar. When a user types in the search box, Mason will match against both the brick's label and any of its tags, so a search for "marketing" can surface a brick whose label is "Hero" as long as that tag is defined.

publicstaticfunctiongetTags(): array
{
return ['hero', 'banner', 'header', 'landing page', 'marketing'];
}

By default, getTags() returns an empty array, so tags are entirely optional.

Rendering Content

You are free to render the content however you see fit. The data is stored in the database as JSON, so you can use the data however you see fit. But the plugin offers a helper method for converting the data to HTML should you choose to use it.

Similar to the form field and entry components, the helper needs to know what bricks are available. You can pass the bricks to the helper as the second argument. See, above about creating a collection of bricks. This will help keep your code DRY.

{!! mason(content: $post->content, bricks: \App\Mason\BrickCollection::make())->toHtml() !!}

There is also a dedicated Render that can be used if you need more control over the rendering process.

useAwcodes\Mason\Support\MasonRenderer;
$renderer = MasonRenderer::make($content)->bricks(\App\Mason\BrickCollection::make());
$renderer->toHtml()
$renderer->toUnsafeHtml();
$renderer->toArray();
$renderer->toText();

Faking Content

When testing, you may want to fake some Mason content. There is a helper method for that as well.

useAwcodes\Mason\Support\Faker;
Faker::make()
->brick(
id: 'section',
config: [
'background_color' => 'white',
'text' => '<h2>This is a heading</h2><p>Just some random text for a paragraph</p>',
'image' => null,
]
)
->brick(
id: 'section',
config: [
'background_color' => 'primary',
'text' => '<h2>This is a heading</h2><p>Just some random text for a paragraph</p>',
'image' => null,
]
)
->asJson(),

Testing

composer test

Development

Mason ships a Workbench — a small Laravel application, powered by Orchestra Testbench, that consumes the package the way a real application would. There is no need for a separate Laravel project.

composer install
composer serve

Then open the panel at /admin and the frontend at /. The login form is prefilled with the seeded development account:

Email: test@example.com
Password: password

See CONTRIBUTING for the rest.

Contributing

Please see CONTRIBUTING for details.

Security Vulnerabilities

Please review our security policy on how to report security vulnerabilities.

Credits

License

The MIT License (MIT). Please see License File for more information.

About

A simple block based drag and drop page / document builder field for Filament.

Topics

Resources

Contributing

Security policy

Stars

246 stars

Watchers

3 watching

Forks

Releases

Sponsor this project

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

Mason

A simple block-based drag and drop page / document builder field for Filament.

Latest VersionMIT LicensedTotal DownloadsGitHub Repo stars

Compatibility

Package VersionFilament Version
0.x3.x
1.x4.x
2.x5.x
3.x4.x, 5.x

Installation

You can install the package via composer:

composer require awcodes/mason

In an effort to align with Filament's theming methodology, you will need to use a custom theme to use this plugin.

Important

If you have not set up a custom theme and are using Filament Panels, follow the instructions in the Filament Docs first. The following applies to both the Panels Package and the standalone Forms package.

After setting up a custom theme, add the plugin's CSS to your theme CSS file or your app's CSS file if using the standalone forms package.

@import'../../../../vendor/awcodes/mason/resources/css/plugin.css';
@source'../../../../vendor/awcodes/mason/resources/**/*.blade.php';

Configuration

You can publish the config file with:

php artisan vendor:publish --tag="mason-config"

These are the contents of the published config file:

return [
'generator' => [
'namespace' => 'App\\Mason',
'views_path' => 'mason',
],
'preview' => [
'layout' => 'mason::iframe-preview',
],
'entry' => [
'layout' => 'mason::iframe-entry',
],
'routes' => [
'middleware' => ['web', 'auth'],
],
];

Auth Guards

By default, Mason uses the web and auth middleware for its routes internally. If you are using a different guard or have multiple guards, you can customize the middleware used by updating the routes.middleware configuration option in the published config file.

'routes' => [
'middleware' => ['web', 'auth:admin'],
],

Usage

Important

Mason uses JSON to store its data in the database, so it is important that you cast the field to either 'array' or 'json' on your model, and it's recommended to store the content as a longText column in the database.

Form Field

In your Filament forms you should use the Mason component. The Mason component accepts a name prop which should be the name of the field in your model, and takes an array of 'bricks' to make available to the editor. If you omit bricks, Mason falls back to its built-in Section brick.

useAwcodes\Mason\Mason;
useAwcodes\Mason\Bricks\Section;
->schema([
Mason::make('content')
->bricks([
Section::class,
]),
])

Field Preview Layout

Since Mason uses an iframe to render in the editor, you should set the preview layout for the field to a view in your application that includes your app's styles. This will ensure that the content in the editor looks similar to how it will look on the front end of your site. If all Mason fields in your forms use the same layout, you can set a default in the config file. Otherwise, you can set it per field like so:

Mason::make('content')
->previewLayout('layouts.mason-preview') // your app's layout
->bricks([...])

Then in your layout file you can include the necessary styles and includes to render the content correctly.

// resources/views/layouts/mason-preview.blade.php
<!DOCTYPE html>
<htmllang="{{str_replace('_', '-', app()->getLocale()) }}">
<head>
<metacharset="utf-8">
<metaname="viewport"content="width=device-width, initial-scale=1">
<title>{{config('app.name') }}</title>
<!-- Fonts -->
<linkrel="preconnect"href="https://fonts.bunny.net">
<linkhref="https://fonts.bunny.net/css?family=instrument-sans:400,500,600"rel="stylesheet" />
<!-- Styles / Scripts -->@vite(['resources/css/app.css', 'resources/js/app.js'])
<!-- Mason Styles -->@masonStyles </head>
<body>
<main>
<!-- Include Mason content rendering -->@include('mason::iframe-preview-content', ['blocks'=>$blocks])
</main>
</body>
</html>

If the blue color used in the editor doesn't work with your design, you can customize it with CSS in your app's CSS file.

#mason-preview-container {
--mason-border-color:rgb(236,72,153);
--mason-controls-background:rgba(0,0,0,0.8);
--mason-button-hover-background:rgba(255,255,255,0.2);
--mason-drop-zone-background:rgba(236,72,153,0.5);
}

Double-Clicking Bricks to Edit

By default, Mason requires you to click the edit button on each brick to edit its content. If you would like to enable double-clicking on bricks to open the edit modal, you can chain the doubleClickToEdit method on the field.

Mason::make('content')
->doubleClickToEdit()
->bricks([...])

Infolist Entry

In your Filament infolists you should use the MasonEntry component. The MasonEntry component accepts a name prop which should be the name of the field in your model, and takes an array of 'bricks' to make available to the entry. As with the field, omitting bricks falls back to the built-in Section brick.

useAwcodes\Mason\MasonEntry;
useAwcodes\Mason\Bricks\Section;
->schema([
MasonEntry::make('content')
->bricks([
Section::class,
]),
])

Entry Preview Layout

Since Mason uses an iframe to render in the infolist, you should set the preview layout for the field to a view in your application that includes your app's styles. This will ensure that the content in the editor looks similar to how it will look on the front end of your site. If all Mason fields in your forms use the same layout, you can set a default in the config file. Otherwise, you can set it per field like so:

MasonEntry::make('content')
->previewLayout('layouts.mason-entry')
->bricks([...])

Then in your layout file you can include the necessary styles and includes to render the content correctly.

// resources/views/layouts/mason-entry.blade.php
<!DOCTYPE html>
<htmllang="{{str_replace('_', '-', app()->getLocale()) }}">
<head>
<metacharset="utf-8">
<metaname="viewport"content="width=device-width, initial-scale=1">
<title>{{config('app.name') }}</title>
<!-- Fonts -->
<linkrel="preconnect"href="https://fonts.bunny.net">
<linkhref="https://fonts.bunny.net/css?family=instrument-sans:400,500,600"rel="stylesheet" />
<!-- Styles / Scripts -->@vite(['resources/css/app.css', 'resources/js/app.js'])
<!-- Mason Entry Styles -->@masonEntryStyles
</head>
<body>
<main>
<!-- Include MasonEntry content rendering -->@include('mason::iframe-entry-content', ['blocks'=>$blocks])
</main>
</body>
</html>

Tips & Tricks

Custom Height

If you find that the default height of the Mason editor or entry is not enough for your use case, you can customize using Filament's default ->extraInputAttrbutes() method on both the Mason field and the MasonEntry component.

Mason::make('content')
->extraInputAttributes(['style' => 'min-height: 30rem;'])
->bricks([...])
MasonEntry::make('content')
->extraInputAttributes(['style' => 'min-height: 40rem;'])
->bricks([...])

Brick Collections

To keep from having to repeat yourself when assigning bricks to the editor and the entry, it would help to create sets of bricks that make sense for their use case. Then you can use that in the bricks method.

class BrickCollection
{
publicstaticfunctionmake(): array
{
return [
NewsletterSignup::class,
Section::class,
Cards::class,
SupportCenter::class,
];
}
}
Mason::make('content')
->bricks(BrickCollection::make())
MasonEntry::make('content')
->bricks(BrickCollection::make())

Sidebar Position

By default, the Mason editor sidebar is positioned on the right side of the editor. If you would like to position it on the left side, you can chain the sidebarPosition method on the field and assign it the new position.

useAwcodes\Mason\Enums\SidebarPosition;
Mason::make('content')
->sidebarPosition(SidebarPosition::Start)
->bricks([...])

Displaying Brick Actions as a Grid

By default, the Mason editor displays the brick actions in a list. If you would like to display them in a grid format, you can chain the displayActionsAsGrid method on the field.

Mason::make('content')
->displayActionsAsGrid()
->bricks([...])

Grouping Bricks

Bricks can be organized into labeled groups in the editor sidebar by wrapping them in a BrickGroup. Groups are collapsible, and searching by brick name or tag will automatically expand any group that contains a match.

useAwcodes\Mason\BrickGroup;
Mason::make('content')
->bricks([
BrickGroup::make('Content')
->bricks([
Section::class,
Grid::class,
]),
BrickGroup::make('Marketing')
->bricks([
Hero::class,
CallToAction::class,
]),
LeadForm::class, // standalone bricks can sit alongside groups
])

Groups and standalone bricks can be freely mixed in the same bricks array. sortBricks() applies to the top-level array and will sort groups alongside standalone bricks by their respective labels.

Sorting Bricks

By default, bricks are sorted in the order they are defined in the bricks array. If you would like to allow users to sort the bricks in the editor, you can chain the sortBricks method on the field.

// Sort ascending (A-Z) by label
Mason::make('content')
->sortBricks('asc')
->bricks([...])
// Or simply (defaults to 'asc')
Mason::make('content')
->sortBricks()
->bricks([...])
// Sort descending (Z-A) by label
Mason::make('content')
->sortBricks('desc')
->bricks([...])

Static Bricks without data or forms

If you would like to create a brick that does not require any data or forms, you can return the view in the toHtml method and set the action to have a hidden modal in the configureBrickAction method in your brick class. Now, when inserting the brick, it will simply add the brick without any configuration.

publicstaticfunctiontoHtml(array$config, ?array$data = null): ?string
{
returnview('mason.static-brick');
}
publicstaticfunctionconfigureBrickAction(Action$action): Action
{
return$action->modalHidden();
}

Light / Dark Mode

If your application supports light and dark mode, you can optionally add support for it in the Mason editor by using the colorModeToggle method on the field. This will add a toggle button to the editor sidebar that allows users to switch between light and dark mode. You can also use the defaultColorMode method to set the default color mode for the editor. One thing to note is that defaultColorMode only sets the initial mode when the editor is loaded. If the user switches modes, their preference will be saved in local storage for future visits.

In order for this to work properly, you will need to ensure that your application's CSS supports manually setting light and dark mode according to the Tailwind CSS documentation on manually controlling color mode.

@custom-variant dark (&:where(.dark, .dark*));
Mason::make('content')
->colorModeToggle()
->defaultColorMode('dark')
->bricks([...])

Creating Bricks

Bricks are nothing more than classes that have an associated view that is rendered in the editor with its data.

To help you get started, there is a make:mason-brick command that will create a new brick for you with the necessary class and blade template in the paths specified in the config file.

php artisan make:mason-brick Section

This will create a new brick in the App\Mason namespace with the class Section and a preview and index blade template in the resources/views/mason directory. Bricks follow the same conventions as Filament RichEditor custom blocks.

useAwcodes\Mason\Brick;
useFilament\Actions\Action;
useFilament\Forms\Components\FileUpload;
useFilament\Forms\Components\Radio;
useFilament\Forms\Components\RichEditor;
useFilament\Forms\Components\ToggleButtons;
useFilament\Schemas\Components\Grid;
useFilament\Schemas\Components\SectionasFilamentSection;
useFilament\Support\Icons\Heroicon;
useIlluminate\Contracts\Support\Htmlable;
useIlluminate\Support\HtmlString;
useThrowable;
class Section extends Brick
{
publicstaticfunctiongetId(): string
{
return'section';
}
publicstaticfunctiongetLabel(): string
{
returnparent::getLabel();
}
publicstaticfunctiongetIcon(): string | Heroicon | Htmlable | null
{
returnnewHtmlString('<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20 20h.01M4 20h.01M8 20h.01M12 20h.01M16 20h.01M20 4h.01M4 4h.01M8 4h.01M12 4h.01M16 4v.01M4 9a1 1 0 0 1 1-1h14a1 1 0 0 1 1 1v6a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1z"/></svg>');
}
publicstaticfunctiongetTags(): array
{
return ['section', 'content', 'layout'];
}
/** * @throws Throwable */publicstaticfunctiontoHtml(array$config, ?array$data = null): ?string
{
returnview('mason::bricks.section.index', [
'background_color' => $config['background_color'] ?? 'white',
'image' => $config['image'] ?? null,
'text' => $config['text'] ?? null,
])->render();
}
publicstaticfunctionconfigureBrickAction(Action$action): Action
{
return$action
->slideOver()
->schema([
Radio::make('background_color'),
FileUpload::make('image'),
RichEditor::make('text'),
]);
}
}

Brick Tags

Bricks can optionally declare tags to improve discoverability when searching the editor sidebar. When a user types in the search box, Mason will match against both the brick's label and any of its tags, so a search for "marketing" can surface a brick whose label is "Hero" as long as that tag is defined.

publicstaticfunctiongetTags(): array
{
return ['hero', 'banner', 'header', 'landing page', 'marketing'];
}

By default, getTags() returns an empty array, so tags are entirely optional.

Rendering Content

You are free to render the content however you see fit. The data is stored in the database as JSON, so you can use the data however you see fit. But the plugin offers a helper method for converting the data to HTML should you choose to use it.

Similar to the form field and entry components, the helper needs to know what bricks are available. You can pass the bricks to the helper as the second argument. See, above about creating a collection of bricks. This will help keep your code DRY.

{!! mason(content: $post->content, bricks: \App\Mason\BrickCollection::make())->toHtml() !!}

There is also a dedicated Render that can be used if you need more control over the rendering process.

useAwcodes\Mason\Support\MasonRenderer;
$renderer = MasonRenderer::make($content)->bricks(\App\Mason\BrickCollection::make());
$renderer->toHtml()
$renderer->toUnsafeHtml();
$renderer->toArray();
$renderer->toText();

Faking Content

When testing, you may want to fake some Mason content. There is a helper method for that as well.

useAwcodes\Mason\Support\Faker;
Faker::make()
->brick(
id: 'section',
config: [
'background_color' => 'white',
'text' => '<h2>This is a heading</h2><p>Just some random text for a paragraph</p>',
'image' => null,
]
)
->brick(
id: 'section',
config: [
'background_color' => 'primary',
'text' => '<h2>This is a heading</h2><p>Just some random text for a paragraph</p>',
'image' => null,
]
)
->asJson(),

Testing

composer test

Development

Mason ships a Workbench — a small Laravel application, powered by Orchestra Testbench, that consumes the package the way a real application would. There is no need for a separate Laravel project.

composer install
composer serve

Then open the panel at /admin and the frontend at /. The login form is prefilled with the seeded development account:

Email: test@example.com
Password: password

See CONTRIBUTING for the rest.

Contributing

Please see CONTRIBUTING for details.

Security Vulnerabilities

Please review our security policy on how to report security vulnerabilities.

Credits

License

The MIT License (MIT). Please see License File for more information.

About

A simple block based drag and drop page / document builder field for Filament.

Topics

Resources

Contributing

Security policy

Stars

246 stars

Watchers

3 watching

Forks

Releases

Sponsor this project

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Repository files navigation

Mason

A simple block-based drag and drop page / document builder field for Filament.

Latest VersionMIT LicensedTotal DownloadsGitHub Repo stars

Compatibility

Package VersionFilament Version
0.x3.x
1.x4.x
2.x5.x
3.x4.x, 5.x

Installation

You can install the package via composer:

composer require awcodes/mason

In an effort to align with Filament's theming methodology, you will need to use a custom theme to use this plugin.

Important

If you have not set up a custom theme and are using Filament Panels, follow the instructions in the Filament Docs first. The following applies to both the Panels Package and the standalone Forms package.

After setting up a custom theme, add the plugin's CSS to your theme CSS file or your app's CSS file if using the standalone forms package.

@import'../../../../vendor/awcodes/mason/resources/css/plugin.css';
@source'../../../../vendor/awcodes/mason/resources/**/*.blade.php';

Configuration

You can publish the config file with:

php artisan vendor:publish --tag="mason-config"

These are the contents of the published config file:

return [
'generator' => [
'namespace' => 'App\\Mason',
'views_path' => 'mason',
],
'preview' => [
'layout' => 'mason::iframe-preview',
],
'entry' => [
'layout' => 'mason::iframe-entry',
],
'routes' => [
'middleware' => ['web', 'auth'],
],
];

Auth Guards

By default, Mason uses the web and auth middleware for its routes internally. If you are using a different guard or have multiple guards, you can customize the middleware used by updating the routes.middleware configuration option in the published config file.

'routes' => [
'middleware' => ['web', 'auth:admin'],
],

Usage

Important

Mason uses JSON to store its data in the database, so it is important that you cast the field to either 'array' or 'json' on your model, and it's recommended to store the content as a longText column in the database.

Form Field

In your Filament forms you should use the Mason component. The Mason component accepts a name prop which should be the name of the field in your model, and takes an array of 'bricks' to make available to the editor. If you omit bricks, Mason falls back to its built-in Section brick.

useAwcodes\Mason\Mason;
useAwcodes\Mason\Bricks\Section;
->schema([
Mason::make('content')
->bricks([
Section::class,
]),
])

Field Preview Layout

Since Mason uses an iframe to render in the editor, you should set the preview layout for the field to a view in your application that includes your app's styles. This will ensure that the content in the editor looks similar to how it will look on the front end of your site. If all Mason fields in your forms use the same layout, you can set a default in the config file. Otherwise, you can set it per field like so:

Mason::make('content')
->previewLayout('layouts.mason-preview') // your app's layout
->bricks([...])

Then in your layout file you can include the necessary styles and includes to render the content correctly.

// resources/views/layouts/mason-preview.blade.php
<!DOCTYPE html>
<htmllang="{{str_replace('_', '-', app()->getLocale()) }}">
<head>
<metacharset="utf-8">
<metaname="viewport"content="width=device-width, initial-scale=1">
<title>{{config('app.name') }}</title>
<!-- Fonts -->
<linkrel="preconnect"href="https://fonts.bunny.net">
<linkhref="https://fonts.bunny.net/css?family=instrument-sans:400,500,600"rel="stylesheet" />
<!-- Styles / Scripts -->@vite(['resources/css/app.css', 'resources/js/app.js'])
<!-- Mason Styles -->@masonStyles </head>
<body>
<main>
<!-- Include Mason content rendering -->@include('mason::iframe-preview-content', ['blocks'=>$blocks])
</main>
</body>
</html>

If the blue color used in the editor doesn't work with your design, you can customize it with CSS in your app's CSS file.

#mason-preview-container {
--mason-border-color:rgb(236,72,153);
--mason-controls-background:rgba(0,0,0,0.8);
--mason-button-hover-background:rgba(255,255,255,0.2);
--mason-drop-zone-background:rgba(236,72,153,0.5);
}

Double-Clicking Bricks to Edit

By default, Mason requires you to click the edit button on each brick to edit its content. If you would like to enable double-clicking on bricks to open the edit modal, you can chain the doubleClickToEdit method on the field.

Mason::make('content')
->doubleClickToEdit()
->bricks([...])

Infolist Entry

In your Filament infolists you should use the MasonEntry component. The MasonEntry component accepts a name prop which should be the name of the field in your model, and takes an array of 'bricks' to make available to the entry. As with the field, omitting bricks falls back to the built-in Section brick.

useAwcodes\Mason\MasonEntry;
useAwcodes\Mason\Bricks\Section;
->schema([
MasonEntry::make('content')
->bricks([
Section::class,
]),
])

Entry Preview Layout

Since Mason uses an iframe to render in the infolist, you should set the preview layout for the field to a view in your application that includes your app's styles. This will ensure that the content in the editor looks similar to how it will look on the front end of your site. If all Mason fields in your forms use the same layout, you can set a default in the config file. Otherwise, you can set it per field like so:

MasonEntry::make('content')
->previewLayout('layouts.mason-entry')
->bricks([...])

Then in your layout file you can include the necessary styles and includes to render the content correctly.

// resources/views/layouts/mason-entry.blade.php
<!DOCTYPE html>
<htmllang="{{str_replace('_', '-', app()->getLocale()) }}">
<head>
<metacharset="utf-8">
<metaname="viewport"content="width=device-width, initial-scale=1">
<title>{{config('app.name') }}</title>
<!-- Fonts -->
<linkrel="preconnect"href="https://fonts.bunny.net">
<linkhref="https://fonts.bunny.net/css?family=instrument-sans:400,500,600"rel="stylesheet" />
<!-- Styles / Scripts -->@vite(['resources/css/app.css', 'resources/js/app.js'])
<!-- Mason Entry Styles -->@masonEntryStyles
</head>
<body>
<main>
<!-- Include MasonEntry content rendering -->@include('mason::iframe-entry-content', ['blocks'=>$blocks])
</main>
</body>
</html>

Tips & Tricks

Custom Height

If you find that the default height of the Mason editor or entry is not enough for your use case, you can customize using Filament's default ->extraInputAttrbutes() method on both the Mason field and the MasonEntry component.

Mason::make('content')
->extraInputAttributes(['style' => 'min-height: 30rem;'])
->bricks([...])
MasonEntry::make('content')
->extraInputAttributes(['style' => 'min-height: 40rem;'])
->bricks([...])

Brick Collections

To keep from having to repeat yourself when assigning bricks to the editor and the entry, it would help to create sets of bricks that make sense for their use case. Then you can use that in the bricks method.

class BrickCollection
{
publicstaticfunctionmake(): array
{
return [
NewsletterSignup::class,
Section::class,
Cards::class,
SupportCenter::class,
];
}
}
Mason::make('content')
->bricks(BrickCollection::make())
MasonEntry::make('content')
->bricks(BrickCollection::make())

Sidebar Position

By default, the Mason editor sidebar is positioned on the right side of the editor. If you would like to position it on the left side, you can chain the sidebarPosition method on the field and assign it the new position.

useAwcodes\Mason\Enums\SidebarPosition;
Mason::make('content')
->sidebarPosition(SidebarPosition::Start)
->bricks([...])

Displaying Brick Actions as a Grid

By default, the Mason editor displays the brick actions in a list. If you would like to display them in a grid format, you can chain the displayActionsAsGrid method on the field.

Mason::make('content')
->displayActionsAsGrid()
->bricks([...])

Grouping Bricks

Bricks can be organized into labeled groups in the editor sidebar by wrapping them in a BrickGroup. Groups are collapsible, and searching by brick name or tag will automatically expand any group that contains a match.

useAwcodes\Mason\BrickGroup;
Mason::make('content')
->bricks([
BrickGroup::make('Content')
->bricks([
Section::class,
Grid::class,
]),
BrickGroup::make('Marketing')
->bricks([
Hero::class,
CallToAction::class,
]),
LeadForm::class, // standalone bricks can sit alongside groups
])

Groups and standalone bricks can be freely mixed in the same bricks array. sortBricks() applies to the top-level array and will sort groups alongside standalone bricks by their respective labels.

Sorting Bricks

By default, bricks are sorted in the order they are defined in the bricks array. If you would like to allow users to sort the bricks in the editor, you can chain the sortBricks method on the field.

// Sort ascending (A-Z) by label
Mason::make('content')
->sortBricks('asc')
->bricks([...])
// Or simply (defaults to 'asc')
Mason::make('content')
->sortBricks()
->bricks([...])
// Sort descending (Z-A) by label
Mason::make('content')
->sortBricks('desc')
->bricks([...])

Static Bricks without data or forms

If you would like to create a brick that does not require any data or forms, you can return the view in the toHtml method and set the action to have a hidden modal in the configureBrickAction method in your brick class. Now, when inserting the brick, it will simply add the brick without any configuration.

publicstaticfunctiontoHtml(array$config, ?array$data = null): ?string
{
returnview('mason.static-brick');
}
publicstaticfunctionconfigureBrickAction(Action$action): Action
{
return$action->modalHidden();
}

Light / Dark Mode

If your application supports light and dark mode, you can optionally add support for it in the Mason editor by using the colorModeToggle method on the field. This will add a toggle button to the editor sidebar that allows users to switch between light and dark mode. You can also use the defaultColorMode method to set the default color mode for the editor. One thing to note is that defaultColorMode only sets the initial mode when the editor is loaded. If the user switches modes, their preference will be saved in local storage for future visits.

In order for this to work properly, you will need to ensure that your application's CSS supports manually setting light and dark mode according to the Tailwind CSS documentation on manually controlling color mode.

@custom-variant dark (&:where(.dark, .dark*));
Mason::make('content')
->colorModeToggle()
->defaultColorMode('dark')
->bricks([...])

Creating Bricks

Bricks are nothing more than classes that have an associated view that is rendered in the editor with its data.

To help you get started, there is a make:mason-brick command that will create a new brick for you with the necessary class and blade template in the paths specified in the config file.

php artisan make:mason-brick Section

This will create a new brick in the App\Mason namespace with the class Section and a preview and index blade template in the resources/views/mason directory. Bricks follow the same conventions as Filament RichEditor custom blocks.

useAwcodes\Mason\Brick;
useFilament\Actions\Action;
useFilament\Forms\Components\FileUpload;
useFilament\Forms\Components\Radio;
useFilament\Forms\Components\RichEditor;
useFilament\Forms\Components\ToggleButtons;
useFilament\Schemas\Components\Grid;
useFilament\Schemas\Components\SectionasFilamentSection;
useFilament\Support\Icons\Heroicon;
useIlluminate\Contracts\Support\Htmlable;
useIlluminate\Support\HtmlString;
useThrowable;
class Section extends Brick
{
publicstaticfunctiongetId(): string
{
return'section';
}
publicstaticfunctiongetLabel(): string
{
returnparent::getLabel();
}
publicstaticfunctiongetIcon(): string | Heroicon | Htmlable | null
{
returnnewHtmlString('<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20 20h.01M4 20h.01M8 20h.01M12 20h.01M16 20h.01M20 4h.01M4 4h.01M8 4h.01M12 4h.01M16 4v.01M4 9a1 1 0 0 1 1-1h14a1 1 0 0 1 1 1v6a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1z"/></svg>');
}
publicstaticfunctiongetTags(): array
{
return ['section', 'content', 'layout'];
}
/** * @throws Throwable */publicstaticfunctiontoHtml(array$config, ?array$data = null): ?string
{
returnview('mason::bricks.section.index', [
'background_color' => $config['background_color'] ?? 'white',
'image' => $config['image'] ?? null,
'text' => $config['text'] ?? null,
])->render();
}
publicstaticfunctionconfigureBrickAction(Action$action): Action
{
return$action
->slideOver()
->schema([
Radio::make('background_color'),
FileUpload::make('image'),
RichEditor::make('text'),
]);
}
}

Brick Tags

Bricks can optionally declare tags to improve discoverability when searching the editor sidebar. When a user types in the search box, Mason will match against both the brick's label and any of its tags, so a search for "marketing" can surface a brick whose label is "Hero" as long as that tag is defined.

publicstaticfunctiongetTags(): array
{
return ['hero', 'banner', 'header', 'landing page', 'marketing'];
}

By default, getTags() returns an empty array, so tags are entirely optional.

Rendering Content

You are free to render the content however you see fit. The data is stored in the database as JSON, so you can use the data however you see fit. But the plugin offers a helper method for converting the data to HTML should you choose to use it.

Similar to the form field and entry components, the helper needs to know what bricks are available. You can pass the bricks to the helper as the second argument. See, above about creating a collection of bricks. This will help keep your code DRY.

{!! mason(content: $post->content, bricks: \App\Mason\BrickCollection::make())->toHtml() !!}

There is also a dedicated Render that can be used if you need more control over the rendering process.

useAwcodes\Mason\Support\MasonRenderer;
$renderer = MasonRenderer::make($content)->bricks(\App\Mason\BrickCollection::make());
$renderer->toHtml()
$renderer->toUnsafeHtml();
$renderer->toArray();
$renderer->toText();

Faking Content

When testing, you may want to fake some Mason content. There is a helper method for that as well.

useAwcodes\Mason\Support\Faker;
Faker::make()
->brick(
id: 'section',
config: [
'background_color' => 'white',
'text' => '<h2>This is a heading</h2><p>Just some random text for a paragraph</p>',
'image' => null,
]
)
->brick(
id: 'section',
config: [
'background_color' => 'primary',
'text' => '<h2>This is a heading</h2><p>Just some random text for a paragraph</p>',
'image' => null,
]
)
->asJson(),

Testing

composer test

Development

Mason ships a Workbench — a small Laravel application, powered by Orchestra Testbench, that consumes the package the way a real application would. There is no need for a separate Laravel project.

composer install
composer serve

Then open the panel at /admin and the frontend at /. The login form is prefilled with the seeded development account:

Email: test@example.com
Password: password

See CONTRIBUTING for the rest.

Contributing

Please see CONTRIBUTING for details.

Security Vulnerabilities

Please review our security policy on how to report security vulnerabilities.

Credits

License

The MIT License (MIT). Please see License File for more information.

About

A simple block based drag and drop page / document builder field for Filament.

Topics

Resources

Contributing

Security policy

Stars

246 stars

Watchers

3 watching

Forks

Releases

Sponsor this project

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

Mason

A simple block-based drag and drop page / document builder field for Filament.

Latest VersionMIT LicensedTotal DownloadsGitHub Repo stars

Compatibility

Package VersionFilament Version
0.x3.x
1.x4.x
2.x5.x
3.x4.x, 5.x

Installation

You can install the package via composer:

composer require awcodes/mason

In an effort to align with Filament's theming methodology, you will need to use a custom theme to use this plugin.

Important

If you have not set up a custom theme and are using Filament Panels, follow the instructions in the Filament Docs first. The following applies to both the Panels Package and the standalone Forms package.

After setting up a custom theme, add the plugin's CSS to your theme CSS file or your app's CSS file if using the standalone forms package.

@import'../../../../vendor/awcodes/mason/resources/css/plugin.css';
@source'../../../../vendor/awcodes/mason/resources/**/*.blade.php';

Configuration

You can publish the config file with:

php artisan vendor:publish --tag="mason-config"

These are the contents of the published config file:

return [
'generator' => [
'namespace' => 'App\\Mason',
'views_path' => 'mason',
],
'preview' => [
'layout' => 'mason::iframe-preview',
],
'entry' => [
'layout' => 'mason::iframe-entry',
],
'routes' => [
'middleware' => ['web', 'auth'],
],
];

Auth Guards

By default, Mason uses the web and auth middleware for its routes internally. If you are using a different guard or have multiple guards, you can customize the middleware used by updating the routes.middleware configuration option in the published config file.

'routes' => [
'middleware' => ['web', 'auth:admin'],
],

Usage

Important

Mason uses JSON to store its data in the database, so it is important that you cast the field to either 'array' or 'json' on your model, and it's recommended to store the content as a longText column in the database.

Form Field

In your Filament forms you should use the Mason component. The Mason component accepts a name prop which should be the name of the field in your model, and takes an array of 'bricks' to make available to the editor. If you omit bricks, Mason falls back to its built-in Section brick.

useAwcodes\Mason\Mason;
useAwcodes\Mason\Bricks\Section;
->schema([
Mason::make('content')
->bricks([
Section::class,
]),
])

Field Preview Layout

Since Mason uses an iframe to render in the editor, you should set the preview layout for the field to a view in your application that includes your app's styles. This will ensure that the content in the editor looks similar to how it will look on the front end of your site. If all Mason fields in your forms use the same layout, you can set a default in the config file. Otherwise, you can set it per field like so:

Mason::make('content')
->previewLayout('layouts.mason-preview') // your app's layout
->bricks([...])

Then in your layout file you can include the necessary styles and includes to render the content correctly.

// resources/views/layouts/mason-preview.blade.php
<!DOCTYPE html>
<htmllang="{{str_replace('_', '-', app()->getLocale()) }}">
<head>
<metacharset="utf-8">
<metaname="viewport"content="width=device-width, initial-scale=1">
<title>{{config('app.name') }}</title>
<!-- Fonts -->
<linkrel="preconnect"href="https://fonts.bunny.net">
<linkhref="https://fonts.bunny.net/css?family=instrument-sans:400,500,600"rel="stylesheet" />
<!-- Styles / Scripts -->@vite(['resources/css/app.css', 'resources/js/app.js'])
<!-- Mason Styles -->@masonStyles </head>
<body>
<main>
<!-- Include Mason content rendering -->@include('mason::iframe-preview-content', ['blocks'=>$blocks])
</main>
</body>
</html>

If the blue color used in the editor doesn't work with your design, you can customize it with CSS in your app's CSS file.

#mason-preview-container {
--mason-border-color:rgb(236,72,153);
--mason-controls-background:rgba(0,0,0,0.8);
--mason-button-hover-background:rgba(255,255,255,0.2);
--mason-drop-zone-background:rgba(236,72,153,0.5);
}

Double-Clicking Bricks to Edit

By default, Mason requires you to click the edit button on each brick to edit its content. If you would like to enable double-clicking on bricks to open the edit modal, you can chain the doubleClickToEdit method on the field.

Mason::make('content')
->doubleClickToEdit()
->bricks([...])

Infolist Entry

In your Filament infolists you should use the MasonEntry component. The MasonEntry component accepts a name prop which should be the name of the field in your model, and takes an array of 'bricks' to make available to the entry. As with the field, omitting bricks falls back to the built-in Section brick.

useAwcodes\Mason\MasonEntry;
useAwcodes\Mason\Bricks\Section;
->schema([
MasonEntry::make('content')
->bricks([
Section::class,
]),
])

Entry Preview Layout

Since Mason uses an iframe to render in the infolist, you should set the preview layout for the field to a view in your application that includes your app's styles. This will ensure that the content in the editor looks similar to how it will look on the front end of your site. If all Mason fields in your forms use the same layout, you can set a default in the config file. Otherwise, you can set it per field like so:

MasonEntry::make('content')
->previewLayout('layouts.mason-entry')
->bricks([...])

Then in your layout file you can include the necessary styles and includes to render the content correctly.

// resources/views/layouts/mason-entry.blade.php
<!DOCTYPE html>
<htmllang="{{str_replace('_', '-', app()->getLocale()) }}">
<head>
<metacharset="utf-8">
<metaname="viewport"content="width=device-width, initial-scale=1">
<title>{{config('app.name') }}</title>
<!-- Fonts -->
<linkrel="preconnect"href="https://fonts.bunny.net">
<linkhref="https://fonts.bunny.net/css?family=instrument-sans:400,500,600"rel="stylesheet" />
<!-- Styles / Scripts -->@vite(['resources/css/app.css', 'resources/js/app.js'])
<!-- Mason Entry Styles -->@masonEntryStyles
</head>
<body>
<main>
<!-- Include MasonEntry content rendering -->@include('mason::iframe-entry-content', ['blocks'=>$blocks])
</main>
</body>
</html>

Tips & Tricks

Custom Height

If you find that the default height of the Mason editor or entry is not enough for your use case, you can customize using Filament's default ->extraInputAttrbutes() method on both the Mason field and the MasonEntry component.

Mason::make('content')
->extraInputAttributes(['style' => 'min-height: 30rem;'])
->bricks([...])
MasonEntry::make('content')
->extraInputAttributes(['style' => 'min-height: 40rem;'])
->bricks([...])

Brick Collections

To keep from having to repeat yourself when assigning bricks to the editor and the entry, it would help to create sets of bricks that make sense for their use case. Then you can use that in the bricks method.

class BrickCollection
{
publicstaticfunctionmake(): array
{
return [
NewsletterSignup::class,
Section::class,
Cards::class,
SupportCenter::class,
];
}
}
Mason::make('content')
->bricks(BrickCollection::make())
MasonEntry::make('content')
->bricks(BrickCollection::make())

Sidebar Position

By default, the Mason editor sidebar is positioned on the right side of the editor. If you would like to position it on the left side, you can chain the sidebarPosition method on the field and assign it the new position.

useAwcodes\Mason\Enums\SidebarPosition;
Mason::make('content')
->sidebarPosition(SidebarPosition::Start)
->bricks([...])

Displaying Brick Actions as a Grid

By default, the Mason editor displays the brick actions in a list. If you would like to display them in a grid format, you can chain the displayActionsAsGrid method on the field.

Mason::make('content')
->displayActionsAsGrid()
->bricks([...])

Grouping Bricks

Bricks can be organized into labeled groups in the editor sidebar by wrapping them in a BrickGroup. Groups are collapsible, and searching by brick name or tag will automatically expand any group that contains a match.

useAwcodes\Mason\BrickGroup;
Mason::make('content')
->bricks([
BrickGroup::make('Content')
->bricks([
Section::class,
Grid::class,
]),
BrickGroup::make('Marketing')
->bricks([
Hero::class,
CallToAction::class,
]),
LeadForm::class, // standalone bricks can sit alongside groups
])

Groups and standalone bricks can be freely mixed in the same bricks array. sortBricks() applies to the top-level array and will sort groups alongside standalone bricks by their respective labels.

Sorting Bricks

By default, bricks are sorted in the order they are defined in the bricks array. If you would like to allow users to sort the bricks in the editor, you can chain the sortBricks method on the field.

// Sort ascending (A-Z) by label
Mason::make('content')
->sortBricks('asc')
->bricks([...])
// Or simply (defaults to 'asc')
Mason::make('content')
->sortBricks()
->bricks([...])
// Sort descending (Z-A) by label
Mason::make('content')
->sortBricks('desc')
->bricks([...])

Static Bricks without data or forms

If you would like to create a brick that does not require any data or forms, you can return the view in the toHtml method and set the action to have a hidden modal in the configureBrickAction method in your brick class. Now, when inserting the brick, it will simply add the brick without any configuration.

publicstaticfunctiontoHtml(array$config, ?array$data = null): ?string
{
returnview('mason.static-brick');
}
publicstaticfunctionconfigureBrickAction(Action$action): Action
{
return$action->modalHidden();
}

Light / Dark Mode

If your application supports light and dark mode, you can optionally add support for it in the Mason editor by using the colorModeToggle method on the field. This will add a toggle button to the editor sidebar that allows users to switch between light and dark mode. You can also use the defaultColorMode method to set the default color mode for the editor. One thing to note is that defaultColorMode only sets the initial mode when the editor is loaded. If the user switches modes, their preference will be saved in local storage for future visits.

In order for this to work properly, you will need to ensure that your application's CSS supports manually setting light and dark mode according to the Tailwind CSS documentation on manually controlling color mode.

@custom-variant dark (&:where(.dark, .dark*));
Mason::make('content')
->colorModeToggle()
->defaultColorMode('dark')
->bricks([...])

Creating Bricks

Bricks are nothing more than classes that have an associated view that is rendered in the editor with its data.

To help you get started, there is a make:mason-brick command that will create a new brick for you with the necessary class and blade template in the paths specified in the config file.

php artisan make:mason-brick Section

This will create a new brick in the App\Mason namespace with the class Section and a preview and index blade template in the resources/views/mason directory. Bricks follow the same conventions as Filament RichEditor custom blocks.

useAwcodes\Mason\Brick;
useFilament\Actions\Action;
useFilament\Forms\Components\FileUpload;
useFilament\Forms\Components\Radio;
useFilament\Forms\Components\RichEditor;
useFilament\Forms\Components\ToggleButtons;
useFilament\Schemas\Components\Grid;
useFilament\Schemas\Components\SectionasFilamentSection;
useFilament\Support\Icons\Heroicon;
useIlluminate\Contracts\Support\Htmlable;
useIlluminate\Support\HtmlString;
useThrowable;
class Section extends Brick
{
publicstaticfunctiongetId(): string
{
return'section';
}
publicstaticfunctiongetLabel(): string
{
returnparent::getLabel();
}
publicstaticfunctiongetIcon(): string | Heroicon | Htmlable | null
{
returnnewHtmlString('<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20 20h.01M4 20h.01M8 20h.01M12 20h.01M16 20h.01M20 4h.01M4 4h.01M8 4h.01M12 4h.01M16 4v.01M4 9a1 1 0 0 1 1-1h14a1 1 0 0 1 1 1v6a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1z"/></svg>');
}
publicstaticfunctiongetTags(): array
{
return ['section', 'content', 'layout'];
}
/** * @throws Throwable */publicstaticfunctiontoHtml(array$config, ?array$data = null): ?string
{
returnview('mason::bricks.section.index', [
'background_color' => $config['background_color'] ?? 'white',
'image' => $config['image'] ?? null,
'text' => $config['text'] ?? null,
])->render();
}
publicstaticfunctionconfigureBrickAction(Action$action): Action
{
return$action
->slideOver()
->schema([
Radio::make('background_color'),
FileUpload::make('image'),
RichEditor::make('text'),
]);
}
}

Brick Tags

Bricks can optionally declare tags to improve discoverability when searching the editor sidebar. When a user types in the search box, Mason will match against both the brick's label and any of its tags, so a search for "marketing" can surface a brick whose label is "Hero" as long as that tag is defined.

publicstaticfunctiongetTags(): array
{
return ['hero', 'banner', 'header', 'landing page', 'marketing'];
}

By default, getTags() returns an empty array, so tags are entirely optional.

Rendering Content

You are free to render the content however you see fit. The data is stored in the database as JSON, so you can use the data however you see fit. But the plugin offers a helper method for converting the data to HTML should you choose to use it.

Similar to the form field and entry components, the helper needs to know what bricks are available. You can pass the bricks to the helper as the second argument. See, above about creating a collection of bricks. This will help keep your code DRY.

{!! mason(content: $post->content, bricks: \App\Mason\BrickCollection::make())->toHtml() !!}

There is also a dedicated Render that can be used if you need more control over the rendering process.

useAwcodes\Mason\Support\MasonRenderer;
$renderer = MasonRenderer::make($content)->bricks(\App\Mason\BrickCollection::make());
$renderer->toHtml()
$renderer->toUnsafeHtml();
$renderer->toArray();
$renderer->toText();

Faking Content

When testing, you may want to fake some Mason content. There is a helper method for that as well.

useAwcodes\Mason\Support\Faker;
Faker::make()
->brick(
id: 'section',
config: [
'background_color' => 'white',
'text' => '<h2>This is a heading</h2><p>Just some random text for a paragraph</p>',
'image' => null,
]
)
->brick(
id: 'section',
config: [
'background_color' => 'primary',
'text' => '<h2>This is a heading</h2><p>Just some random text for a paragraph</p>',
'image' => null,
]
)
->asJson(),

Testing

composer test

Development

Mason ships a Workbench — a small Laravel application, powered by Orchestra Testbench, that consumes the package the way a real application would. There is no need for a separate Laravel project.

composer install
composer serve

Then open the panel at /admin and the frontend at /. The login form is prefilled with the seeded development account:

Email: test@example.com
Password: password

See CONTRIBUTING for the rest.

Contributing

Please see CONTRIBUTING for details.

Security Vulnerabilities

Please review our security policy on how to report security vulnerabilities.

Credits

License

The MIT License (MIT). Please see License File for more information.

About

A simple block based drag and drop page / document builder field for Filament.

Topics

Resources

Contributing

Security policy

Stars

246 stars

Watchers

3 watching

Forks

Releases

Sponsor this project

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

Mason

A simple block-based drag and drop page / document builder field for Filament.

Latest VersionMIT LicensedTotal DownloadsGitHub Repo stars

Compatibility

Package VersionFilament Version
0.x3.x
1.x4.x
2.x5.x
3.x4.x, 5.x

Installation

You can install the package via composer:

composer require awcodes/mason

In an effort to align with Filament's theming methodology, you will need to use a custom theme to use this plugin.

Important

If you have not set up a custom theme and are using Filament Panels, follow the instructions in the Filament Docs first. The following applies to both the Panels Package and the standalone Forms package.

After setting up a custom theme, add the plugin's CSS to your theme CSS file or your app's CSS file if using the standalone forms package.

@import'../../../../vendor/awcodes/mason/resources/css/plugin.css';
@source'../../../../vendor/awcodes/mason/resources/**/*.blade.php';

Configuration

You can publish the config file with:

php artisan vendor:publish --tag="mason-config"

These are the contents of the published config file:

return [
'generator' => [
'namespace' => 'App\\Mason',
'views_path' => 'mason',
],
'preview' => [
'layout' => 'mason::iframe-preview',
],
'entry' => [
'layout' => 'mason::iframe-entry',
],
'routes' => [
'middleware' => ['web', 'auth'],
],
];

Auth Guards

By default, Mason uses the web and auth middleware for its routes internally. If you are using a different guard or have multiple guards, you can customize the middleware used by updating the routes.middleware configuration option in the published config file.

'routes' => [
'middleware' => ['web', 'auth:admin'],
],

Usage

Important

Mason uses JSON to store its data in the database, so it is important that you cast the field to either 'array' or 'json' on your model, and it's recommended to store the content as a longText column in the database.

Form Field

In your Filament forms you should use the Mason component. The Mason component accepts a name prop which should be the name of the field in your model, and takes an array of 'bricks' to make available to the editor. If you omit bricks, Mason falls back to its built-in Section brick.

useAwcodes\Mason\Mason;
useAwcodes\Mason\Bricks\Section;
->schema([
Mason::make('content')
->bricks([
Section::class,
]),
])

Field Preview Layout

Since Mason uses an iframe to render in the editor, you should set the preview layout for the field to a view in your application that includes your app's styles. This will ensure that the content in the editor looks similar to how it will look on the front end of your site. If all Mason fields in your forms use the same layout, you can set a default in the config file. Otherwise, you can set it per field like so:

Mason::make('content')
->previewLayout('layouts.mason-preview') // your app's layout
->bricks([...])

Then in your layout file you can include the necessary styles and includes to render the content correctly.

// resources/views/layouts/mason-preview.blade.php
<!DOCTYPE html>
<htmllang="{{str_replace('_', '-', app()->getLocale()) }}">
<head>
<metacharset="utf-8">
<metaname="viewport"content="width=device-width, initial-scale=1">
<title>{{config('app.name') }}</title>
<!-- Fonts -->
<linkrel="preconnect"href="https://fonts.bunny.net">
<linkhref="https://fonts.bunny.net/css?family=instrument-sans:400,500,600"rel="stylesheet" />
<!-- Styles / Scripts -->@vite(['resources/css/app.css', 'resources/js/app.js'])
<!-- Mason Styles -->@masonStyles </head>
<body>
<main>
<!-- Include Mason content rendering -->@include('mason::iframe-preview-content', ['blocks'=>$blocks])
</main>
</body>
</html>

If the blue color used in the editor doesn't work with your design, you can customize it with CSS in your app's CSS file.

#mason-preview-container {
--mason-border-color:rgb(236,72,153);
--mason-controls-background:rgba(0,0,0,0.8);
--mason-button-hover-background:rgba(255,255,255,0.2);
--mason-drop-zone-background:rgba(236,72,153,0.5);
}

Double-Clicking Bricks to Edit

By default, Mason requires you to click the edit button on each brick to edit its content. If you would like to enable double-clicking on bricks to open the edit modal, you can chain the doubleClickToEdit method on the field.

Mason::make('content')
->doubleClickToEdit()
->bricks([...])

Infolist Entry

In your Filament infolists you should use the MasonEntry component. The MasonEntry component accepts a name prop which should be the name of the field in your model, and takes an array of 'bricks' to make available to the entry. As with the field, omitting bricks falls back to the built-in Section brick.

useAwcodes\Mason\MasonEntry;
useAwcodes\Mason\Bricks\Section;
->schema([
MasonEntry::make('content')
->bricks([
Section::class,
]),
])

Entry Preview Layout

Since Mason uses an iframe to render in the infolist, you should set the preview layout for the field to a view in your application that includes your app's styles. This will ensure that the content in the editor looks similar to how it will look on the front end of your site. If all Mason fields in your forms use the same layout, you can set a default in the config file. Otherwise, you can set it per field like so:

MasonEntry::make('content')
->previewLayout('layouts.mason-entry')
->bricks([...])

Then in your layout file you can include the necessary styles and includes to render the content correctly.

// resources/views/layouts/mason-entry.blade.php
<!DOCTYPE html>
<htmllang="{{str_replace('_', '-', app()->getLocale()) }}">
<head>
<metacharset="utf-8">
<metaname="viewport"content="width=device-width, initial-scale=1">
<title>{{config('app.name') }}</title>
<!-- Fonts -->
<linkrel="preconnect"href="https://fonts.bunny.net">
<linkhref="https://fonts.bunny.net/css?family=instrument-sans:400,500,600"rel="stylesheet" />
<!-- Styles / Scripts -->@vite(['resources/css/app.css', 'resources/js/app.js'])
<!-- Mason Entry Styles -->@masonEntryStyles
</head>
<body>
<main>
<!-- Include MasonEntry content rendering -->@include('mason::iframe-entry-content', ['blocks'=>$blocks])
</main>
</body>
</html>

Tips & Tricks

Custom Height

If you find that the default height of the Mason editor or entry is not enough for your use case, you can customize using Filament's default ->extraInputAttrbutes() method on both the Mason field and the MasonEntry component.

Mason::make('content')
->extraInputAttributes(['style' => 'min-height: 30rem;'])
->bricks([...])
MasonEntry::make('content')
->extraInputAttributes(['style' => 'min-height: 40rem;'])
->bricks([...])

Brick Collections

To keep from having to repeat yourself when assigning bricks to the editor and the entry, it would help to create sets of bricks that make sense for their use case. Then you can use that in the bricks method.

class BrickCollection
{
publicstaticfunctionmake(): array
{
return [
NewsletterSignup::class,
Section::class,
Cards::class,
SupportCenter::class,
];
}
}
Mason::make('content')
->bricks(BrickCollection::make())
MasonEntry::make('content')
->bricks(BrickCollection::make())

Sidebar Position

By default, the Mason editor sidebar is positioned on the right side of the editor. If you would like to position it on the left side, you can chain the sidebarPosition method on the field and assign it the new position.

useAwcodes\Mason\Enums\SidebarPosition;
Mason::make('content')
->sidebarPosition(SidebarPosition::Start)
->bricks([...])

Displaying Brick Actions as a Grid

By default, the Mason editor displays the brick actions in a list. If you would like to display them in a grid format, you can chain the displayActionsAsGrid method on the field.

Mason::make('content')
->displayActionsAsGrid()
->bricks([...])

Grouping Bricks

Bricks can be organized into labeled groups in the editor sidebar by wrapping them in a BrickGroup. Groups are collapsible, and searching by brick name or tag will automatically expand any group that contains a match.

useAwcodes\Mason\BrickGroup;
Mason::make('content')
->bricks([
BrickGroup::make('Content')
->bricks([
Section::class,
Grid::class,
]),
BrickGroup::make('Marketing')
->bricks([
Hero::class,
CallToAction::class,
]),
LeadForm::class, // standalone bricks can sit alongside groups
])

Groups and standalone bricks can be freely mixed in the same bricks array. sortBricks() applies to the top-level array and will sort groups alongside standalone bricks by their respective labels.

Sorting Bricks

By default, bricks are sorted in the order they are defined in the bricks array. If you would like to allow users to sort the bricks in the editor, you can chain the sortBricks method on the field.

// Sort ascending (A-Z) by label
Mason::make('content')
->sortBricks('asc')
->bricks([...])
// Or simply (defaults to 'asc')
Mason::make('content')
->sortBricks()
->bricks([...])
// Sort descending (Z-A) by label
Mason::make('content')
->sortBricks('desc')
->bricks([...])

Static Bricks without data or forms

If you would like to create a brick that does not require any data or forms, you can return the view in the toHtml method and set the action to have a hidden modal in the configureBrickAction method in your brick class. Now, when inserting the brick, it will simply add the brick without any configuration.

publicstaticfunctiontoHtml(array$config, ?array$data = null): ?string
{
returnview('mason.static-brick');
}
publicstaticfunctionconfigureBrickAction(Action$action): Action
{
return$action->modalHidden();
}

Light / Dark Mode

If your application supports light and dark mode, you can optionally add support for it in the Mason editor by using the colorModeToggle method on the field. This will add a toggle button to the editor sidebar that allows users to switch between light and dark mode. You can also use the defaultColorMode method to set the default color mode for the editor. One thing to note is that defaultColorMode only sets the initial mode when the editor is loaded. If the user switches modes, their preference will be saved in local storage for future visits.

In order for this to work properly, you will need to ensure that your application's CSS supports manually setting light and dark mode according to the Tailwind CSS documentation on manually controlling color mode.

@custom-variant dark (&:where(.dark, .dark*));
Mason::make('content')
->colorModeToggle()
->defaultColorMode('dark')
->bricks([...])

Creating Bricks

Bricks are nothing more than classes that have an associated view that is rendered in the editor with its data.

To help you get started, there is a make:mason-brick command that will create a new brick for you with the necessary class and blade template in the paths specified in the config file.

php artisan make:mason-brick Section

This will create a new brick in the App\Mason namespace with the class Section and a preview and index blade template in the resources/views/mason directory. Bricks follow the same conventions as Filament RichEditor custom blocks.

useAwcodes\Mason\Brick;
useFilament\Actions\Action;
useFilament\Forms\Components\FileUpload;
useFilament\Forms\Components\Radio;
useFilament\Forms\Components\RichEditor;
useFilament\Forms\Components\ToggleButtons;
useFilament\Schemas\Components\Grid;
useFilament\Schemas\Components\SectionasFilamentSection;
useFilament\Support\Icons\Heroicon;
useIlluminate\Contracts\Support\Htmlable;
useIlluminate\Support\HtmlString;
useThrowable;
class Section extends Brick
{
publicstaticfunctiongetId(): string
{
return'section';
}
publicstaticfunctiongetLabel(): string
{
returnparent::getLabel();
}
publicstaticfunctiongetIcon(): string | Heroicon | Htmlable | null
{
returnnewHtmlString('<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20 20h.01M4 20h.01M8 20h.01M12 20h.01M16 20h.01M20 4h.01M4 4h.01M8 4h.01M12 4h.01M16 4v.01M4 9a1 1 0 0 1 1-1h14a1 1 0 0 1 1 1v6a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1z"/></svg>');
}
publicstaticfunctiongetTags(): array
{
return ['section', 'content', 'layout'];
}
/** * @throws Throwable */publicstaticfunctiontoHtml(array$config, ?array$data = null): ?string
{
returnview('mason::bricks.section.index', [
'background_color' => $config['background_color'] ?? 'white',
'image' => $config['image'] ?? null,
'text' => $config['text'] ?? null,
])->render();
}
publicstaticfunctionconfigureBrickAction(Action$action): Action
{
return$action
->slideOver()
->schema([
Radio::make('background_color'),
FileUpload::make('image'),
RichEditor::make('text'),
]);
}
}

Brick Tags

Bricks can optionally declare tags to improve discoverability when searching the editor sidebar. When a user types in the search box, Mason will match against both the brick's label and any of its tags, so a search for "marketing" can surface a brick whose label is "Hero" as long as that tag is defined.

publicstaticfunctiongetTags(): array
{
return ['hero', 'banner', 'header', 'landing page', 'marketing'];
}

By default, getTags() returns an empty array, so tags are entirely optional.

Rendering Content

You are free to render the content however you see fit. The data is stored in the database as JSON, so you can use the data however you see fit. But the plugin offers a helper method for converting the data to HTML should you choose to use it.

Similar to the form field and entry components, the helper needs to know what bricks are available. You can pass the bricks to the helper as the second argument. See, above about creating a collection of bricks. This will help keep your code DRY.

{!! mason(content: $post->content, bricks: \App\Mason\BrickCollection::make())->toHtml() !!}

There is also a dedicated Render that can be used if you need more control over the rendering process.

useAwcodes\Mason\Support\MasonRenderer;
$renderer = MasonRenderer::make($content)->bricks(\App\Mason\BrickCollection::make());
$renderer->toHtml()
$renderer->toUnsafeHtml();
$renderer->toArray();
$renderer->toText();

Faking Content

When testing, you may want to fake some Mason content. There is a helper method for that as well.

useAwcodes\Mason\Support\Faker;
Faker::make()
->brick(
id: 'section',
config: [
'background_color' => 'white',
'text' => '<h2>This is a heading</h2><p>Just some random text for a paragraph</p>',
'image' => null,
]
)
->brick(
id: 'section',
config: [
'background_color' => 'primary',
'text' => '<h2>This is a heading</h2><p>Just some random text for a paragraph</p>',
'image' => null,
]
)
->asJson(),

Testing

composer test

Development

Mason ships a Workbench — a small Laravel application, powered by Orchestra Testbench, that consumes the package the way a real application would. There is no need for a separate Laravel project.

composer install
composer serve

Then open the panel at /admin and the frontend at /. The login form is prefilled with the seeded development account:

Email: test@example.com
Password: password

See CONTRIBUTING for the rest.

Contributing

Please see CONTRIBUTING for details.

Security Vulnerabilities

Please review our security policy on how to report security vulnerabilities.

Credits

License

The MIT License (MIT). Please see License File for more information.

About

A simple block based drag and drop page / document builder field for Filament.

Topics

Resources

Contributing

Security policy

Stars

246 stars

Watchers

3 watching

Forks

Releases

Sponsor this project

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Repository files navigation

Mason

A simple block-based drag and drop page / document builder field for Filament.

Latest VersionMIT LicensedTotal DownloadsGitHub Repo stars

Compatibility

Package VersionFilament Version
0.x3.x
1.x4.x
2.x5.x
3.x4.x, 5.x

Installation

You can install the package via composer:

composer require awcodes/mason

In an effort to align with Filament's theming methodology, you will need to use a custom theme to use this plugin.

Important

If you have not set up a custom theme and are using Filament Panels, follow the instructions in the Filament Docs first. The following applies to both the Panels Package and the standalone Forms package.

After setting up a custom theme, add the plugin's CSS to your theme CSS file or your app's CSS file if using the standalone forms package.

@import'../../../../vendor/awcodes/mason/resources/css/plugin.css';
@source'../../../../vendor/awcodes/mason/resources/**/*.blade.php';

Configuration

You can publish the config file with:

php artisan vendor:publish --tag="mason-config"

These are the contents of the published config file:

return [
'generator' => [
'namespace' => 'App\\Mason',
'views_path' => 'mason',
],
'preview' => [
'layout' => 'mason::iframe-preview',
],
'entry' => [
'layout' => 'mason::iframe-entry',
],
'routes' => [
'middleware' => ['web', 'auth'],
],
];

Auth Guards

By default, Mason uses the web and auth middleware for its routes internally. If you are using a different guard or have multiple guards, you can customize the middleware used by updating the routes.middleware configuration option in the published config file.

'routes' => [
'middleware' => ['web', 'auth:admin'],
],

Usage

Important

Mason uses JSON to store its data in the database, so it is important that you cast the field to either 'array' or 'json' on your model, and it's recommended to store the content as a longText column in the database.

Form Field

In your Filament forms you should use the Mason component. The Mason component accepts a name prop which should be the name of the field in your model, and takes an array of 'bricks' to make available to the editor. If you omit bricks, Mason falls back to its built-in Section brick.

useAwcodes\Mason\Mason;
useAwcodes\Mason\Bricks\Section;
->schema([
Mason::make('content')
->bricks([
Section::class,
]),
])

Field Preview Layout

Since Mason uses an iframe to render in the editor, you should set the preview layout for the field to a view in your application that includes your app's styles. This will ensure that the content in the editor looks similar to how it will look on the front end of your site. If all Mason fields in your forms use the same layout, you can set a default in the config file. Otherwise, you can set it per field like so:

Mason::make('content')
->previewLayout('layouts.mason-preview') // your app's layout
->bricks([...])

Then in your layout file you can include the necessary styles and includes to render the content correctly.

// resources/views/layouts/mason-preview.blade.php
<!DOCTYPE html>
<htmllang="{{str_replace('_', '-', app()->getLocale()) }}">
<head>
<metacharset="utf-8">
<metaname="viewport"content="width=device-width, initial-scale=1">
<title>{{config('app.name') }}</title>
<!-- Fonts -->
<linkrel="preconnect"href="https://fonts.bunny.net">
<linkhref="https://fonts.bunny.net/css?family=instrument-sans:400,500,600"rel="stylesheet" />
<!-- Styles / Scripts -->@vite(['resources/css/app.css', 'resources/js/app.js'])
<!-- Mason Styles -->@masonStyles </head>
<body>
<main>
<!-- Include Mason content rendering -->@include('mason::iframe-preview-content', ['blocks'=>$blocks])
</main>
</body>
</html>

If the blue color used in the editor doesn't work with your design, you can customize it with CSS in your app's CSS file.

#mason-preview-container {
--mason-border-color:rgb(236,72,153);
--mason-controls-background:rgba(0,0,0,0.8);
--mason-button-hover-background:rgba(255,255,255,0.2);
--mason-drop-zone-background:rgba(236,72,153,0.5);
}

Double-Clicking Bricks to Edit

By default, Mason requires you to click the edit button on each brick to edit its content. If you would like to enable double-clicking on bricks to open the edit modal, you can chain the doubleClickToEdit method on the field.

Mason::make('content')
->doubleClickToEdit()
->bricks([...])

Infolist Entry

In your Filament infolists you should use the MasonEntry component. The MasonEntry component accepts a name prop which should be the name of the field in your model, and takes an array of 'bricks' to make available to the entry. As with the field, omitting bricks falls back to the built-in Section brick.

useAwcodes\Mason\MasonEntry;
useAwcodes\Mason\Bricks\Section;
->schema([
MasonEntry::make('content')
->bricks([
Section::class,
]),
])

Entry Preview Layout

Since Mason uses an iframe to render in the infolist, you should set the preview layout for the field to a view in your application that includes your app's styles. This will ensure that the content in the editor looks similar to how it will look on the front end of your site. If all Mason fields in your forms use the same layout, you can set a default in the config file. Otherwise, you can set it per field like so:

MasonEntry::make('content')
->previewLayout('layouts.mason-entry')
->bricks([...])

Then in your layout file you can include the necessary styles and includes to render the content correctly.

// resources/views/layouts/mason-entry.blade.php
<!DOCTYPE html>
<htmllang="{{str_replace('_', '-', app()->getLocale()) }}">
<head>
<metacharset="utf-8">
<metaname="viewport"content="width=device-width, initial-scale=1">
<title>{{config('app.name') }}</title>
<!-- Fonts -->
<linkrel="preconnect"href="https://fonts.bunny.net">
<linkhref="https://fonts.bunny.net/css?family=instrument-sans:400,500,600"rel="stylesheet" />
<!-- Styles / Scripts -->@vite(['resources/css/app.css', 'resources/js/app.js'])
<!-- Mason Entry Styles -->@masonEntryStyles
</head>
<body>
<main>
<!-- Include MasonEntry content rendering -->@include('mason::iframe-entry-content', ['blocks'=>$blocks])
</main>
</body>
</html>

Tips & Tricks

Custom Height

If you find that the default height of the Mason editor or entry is not enough for your use case, you can customize using Filament's default ->extraInputAttrbutes() method on both the Mason field and the MasonEntry component.

Mason::make('content')
->extraInputAttributes(['style' => 'min-height: 30rem;'])
->bricks([...])
MasonEntry::make('content')
->extraInputAttributes(['style' => 'min-height: 40rem;'])
->bricks([...])

Brick Collections

To keep from having to repeat yourself when assigning bricks to the editor and the entry, it would help to create sets of bricks that make sense for their use case. Then you can use that in the bricks method.

class BrickCollection
{
publicstaticfunctionmake(): array
{
return [
NewsletterSignup::class,
Section::class,
Cards::class,
SupportCenter::class,
];
}
}
Mason::make('content')
->bricks(BrickCollection::make())
MasonEntry::make('content')
->bricks(BrickCollection::make())

Sidebar Position

By default, the Mason editor sidebar is positioned on the right side of the editor. If you would like to position it on the left side, you can chain the sidebarPosition method on the field and assign it the new position.

useAwcodes\Mason\Enums\SidebarPosition;
Mason::make('content')
->sidebarPosition(SidebarPosition::Start)
->bricks([...])

Displaying Brick Actions as a Grid

By default, the Mason editor displays the brick actions in a list. If you would like to display them in a grid format, you can chain the displayActionsAsGrid method on the field.

Mason::make('content')
->displayActionsAsGrid()
->bricks([...])

Grouping Bricks

Bricks can be organized into labeled groups in the editor sidebar by wrapping them in a BrickGroup. Groups are collapsible, and searching by brick name or tag will automatically expand any group that contains a match.

useAwcodes\Mason\BrickGroup;
Mason::make('content')
->bricks([
BrickGroup::make('Content')
->bricks([
Section::class,
Grid::class,
]),
BrickGroup::make('Marketing')
->bricks([
Hero::class,
CallToAction::class,
]),
LeadForm::class, // standalone bricks can sit alongside groups
])

Groups and standalone bricks can be freely mixed in the same bricks array. sortBricks() applies to the top-level array and will sort groups alongside standalone bricks by their respective labels.

Sorting Bricks

By default, bricks are sorted in the order they are defined in the bricks array. If you would like to allow users to sort the bricks in the editor, you can chain the sortBricks method on the field.

// Sort ascending (A-Z) by label
Mason::make('content')
->sortBricks('asc')
->bricks([...])
// Or simply (defaults to 'asc')
Mason::make('content')
->sortBricks()
->bricks([...])
// Sort descending (Z-A) by label
Mason::make('content')
->sortBricks('desc')
->bricks([...])

Static Bricks without data or forms

If you would like to create a brick that does not require any data or forms, you can return the view in the toHtml method and set the action to have a hidden modal in the configureBrickAction method in your brick class. Now, when inserting the brick, it will simply add the brick without any configuration.

publicstaticfunctiontoHtml(array$config, ?array$data = null): ?string
{
returnview('mason.static-brick');
}
publicstaticfunctionconfigureBrickAction(Action$action): Action
{
return$action->modalHidden();
}

Light / Dark Mode

If your application supports light and dark mode, you can optionally add support for it in the Mason editor by using the colorModeToggle method on the field. This will add a toggle button to the editor sidebar that allows users to switch between light and dark mode. You can also use the defaultColorMode method to set the default color mode for the editor. One thing to note is that defaultColorMode only sets the initial mode when the editor is loaded. If the user switches modes, their preference will be saved in local storage for future visits.

In order for this to work properly, you will need to ensure that your application's CSS supports manually setting light and dark mode according to the Tailwind CSS documentation on manually controlling color mode.

@custom-variant dark (&:where(.dark, .dark*));
Mason::make('content')
->colorModeToggle()
->defaultColorMode('dark')
->bricks([...])

Creating Bricks

Bricks are nothing more than classes that have an associated view that is rendered in the editor with its data.

To help you get started, there is a make:mason-brick command that will create a new brick for you with the necessary class and blade template in the paths specified in the config file.

php artisan make:mason-brick Section

This will create a new brick in the App\Mason namespace with the class Section and a preview and index blade template in the resources/views/mason directory. Bricks follow the same conventions as Filament RichEditor custom blocks.

useAwcodes\Mason\Brick;
useFilament\Actions\Action;
useFilament\Forms\Components\FileUpload;
useFilament\Forms\Components\Radio;
useFilament\Forms\Components\RichEditor;
useFilament\Forms\Components\ToggleButtons;
useFilament\Schemas\Components\Grid;
useFilament\Schemas\Components\SectionasFilamentSection;
useFilament\Support\Icons\Heroicon;
useIlluminate\Contracts\Support\Htmlable;
useIlluminate\Support\HtmlString;
useThrowable;
class Section extends Brick
{
publicstaticfunctiongetId(): string
{
return'section';
}
publicstaticfunctiongetLabel(): string
{
returnparent::getLabel();
}
publicstaticfunctiongetIcon(): string | Heroicon | Htmlable | null
{
returnnewHtmlString('<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20 20h.01M4 20h.01M8 20h.01M12 20h.01M16 20h.01M20 4h.01M4 4h.01M8 4h.01M12 4h.01M16 4v.01M4 9a1 1 0 0 1 1-1h14a1 1 0 0 1 1 1v6a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1z"/></svg>');
}
publicstaticfunctiongetTags(): array
{
return ['section', 'content', 'layout'];
}
/** * @throws Throwable */publicstaticfunctiontoHtml(array$config, ?array$data = null): ?string
{
returnview('mason::bricks.section.index', [
'background_color' => $config['background_color'] ?? 'white',
'image' => $config['image'] ?? null,
'text' => $config['text'] ?? null,
])->render();
}
publicstaticfunctionconfigureBrickAction(Action$action): Action
{
return$action
->slideOver()
->schema([
Radio::make('background_color'),
FileUpload::make('image'),
RichEditor::make('text'),
]);
}
}

Brick Tags

Bricks can optionally declare tags to improve discoverability when searching the editor sidebar. When a user types in the search box, Mason will match against both the brick's label and any of its tags, so a search for "marketing" can surface a brick whose label is "Hero" as long as that tag is defined.

publicstaticfunctiongetTags(): array
{
return ['hero', 'banner', 'header', 'landing page', 'marketing'];
}

By default, getTags() returns an empty array, so tags are entirely optional.

Rendering Content

You are free to render the content however you see fit. The data is stored in the database as JSON, so you can use the data however you see fit. But the plugin offers a helper method for converting the data to HTML should you choose to use it.

Similar to the form field and entry components, the helper needs to know what bricks are available. You can pass the bricks to the helper as the second argument. See, above about creating a collection of bricks. This will help keep your code DRY.

{!! mason(content: $post->content, bricks: \App\Mason\BrickCollection::make())->toHtml() !!}

There is also a dedicated Render that can be used if you need more control over the rendering process.

useAwcodes\Mason\Support\MasonRenderer;
$renderer = MasonRenderer::make($content)->bricks(\App\Mason\BrickCollection::make());
$renderer->toHtml()
$renderer->toUnsafeHtml();
$renderer->toArray();
$renderer->toText();

Faking Content

When testing, you may want to fake some Mason content. There is a helper method for that as well.

useAwcodes\Mason\Support\Faker;
Faker::make()
->brick(
id: 'section',
config: [
'background_color' => 'white',
'text' => '<h2>This is a heading</h2><p>Just some random text for a paragraph</p>',
'image' => null,
]
)
->brick(
id: 'section',
config: [
'background_color' => 'primary',
'text' => '<h2>This is a heading</h2><p>Just some random text for a paragraph</p>',
'image' => null,
]
)
->asJson(),

Testing

composer test

Development

Mason ships a Workbench — a small Laravel application, powered by Orchestra Testbench, that consumes the package the way a real application would. There is no need for a separate Laravel project.

composer install
composer serve

Then open the panel at /admin and the frontend at /. The login form is prefilled with the seeded development account:

Email: test@example.com
Password: password

See CONTRIBUTING for the rest.

Contributing

Please see CONTRIBUTING for details.

Security Vulnerabilities

Please review our security policy on how to report security vulnerabilities.

Credits

License

The MIT License (MIT). Please see License File for more information.

About

A simple block based drag and drop page / document builder field for Filament.

Topics

Resources

Contributing

Security policy

Stars

246 stars

Watchers

3 watching

Forks

Releases

Sponsor this project

Packages

Used by

Contributors

Languages