Repository navigation
feat(NB-3148): email block composer, block registry and block library - #76
Merged
Merged
Conversation
- EmailBlockDefinition contract, AbstractEmailBlock, seven block types - EmailBlockRegistry (config + runtime registration) and EmailLayoutRenderer - layout/layout_preview views, block row views, unsubscribe row - EmailBlock model and block library resource - block mode toggle and Builder on the template form; package trait renders layouts - registry and renderer tests Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- layout mailable: block order, support block, footer, unsubscribe row, classic regression - builder: one block per registry entry, labels with summaries, previews via renderer - template form: quote toggle hidden, block mode seeding, null layout when off, validation - block library resource: list, create, unique name, edit, delete/restore, preview Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- README section on block mode, the block library, image disk and adding a block type - CHANGELOG 5.7.0 entry (unreleased; tag pending) - Pint (laravel preset) on the new files Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…tion Adds search/sort/filter coverage for the new EmailBlockResource table, plus the token-URL regex happy path and hero requiredWith pairing that the composer validation test only exercised the failure case for. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Render Builder block previews inside a sandboxed srcdoc iframe (no scripts, opaque origin) instead of inline markup in the admin page - Restrict block image uploads to PNG, JPEG, GIF and WebP - README: register a policy for the EmailBlock model - Tests: escaped srcdoc, sandbox attribute, raster-only uploads Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
cannycookie
marked this pull request as ready for review
October 2, 2026 03:34
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Part 1 of NB-3148 (modular email templates): a block composer in the email-templates package. A template can switch its body from the single TinyMCE
contentfield to an ordered list of typed blocks (layout). Templates with no layout send exactly as before.Blocks\Contracts\EmailBlockDefinition,Blocks\AbstractEmailBlock(token replacement, public image URLs, rendering throughview($mode)),Blocks\BlockRenderContext,Enums\BlockRenderMode(Emailonly).Blocks\EmailBlockRegistry(singleton) lists block types fromconfig('filament-email-templates.block_types')orDEFAULT_TYPES, plusregister()at runtime; a later class with the samename()replaces the earlier one in place.src/Blocks/Types): Text, Hero, Two columns, Button, Image, Divider / spacer, Saved block. Saved blocks render the library block inline, cannot nest, and are skipped (with a warning) when inactive, deleted or missing.Layout\EmailLayoutRendererrenders a layout in order; unknown types and malformed items are skipped. The send path, template preview, block library preview and Builder block previews all use it.email/layout.blade.phpandlayout_preview.blade.php(header, hero title, blocks, support block, footer, unsubscribe),email/blocks/*.blade.php, shared row partials_block_open/_block_close/_block_button,_support_block_standalone,_unsubscribe,forms/block-preview.blade.php.layoutJSON column on templates (fillable, array cast),EmailBlockmodel +vb_email_blockstable; migration stubs plus sqlite test mirrors;EmailTemplate::usesLayout()/renderViewPath().layout = nulland keepscontent.EmailBlockResource(list, create, edit, soft delete/restore, trashed filter, active filter, preview slide-over), registered by the plugin.BuildGenericEmailrenders the layout and passesunsubscribeUrlwhen the mailable has one (CampaignMaildoes), so filament-campaigns needs no change.## 5.7.0(unreleased).Security fix (review cycle)
srcdociframe (sandboxwith noallow-scripts/allow-same-origin, escaped attribute,title="Email block preview", fixed 320px height) instead of inline markup in the admin page. Stored rich text is not sanitised server-side, so inline previews let an editor's<script>oronerrorrun in other admins' sessions. Sent email output is unchanged. Filament's edit overlay sits over the preview, so click-to-edit still works; a block taller than 320px is cut off in the preview (an auto height would need scripts in the frame).Visualbuilder\EmailTemplates\Models\EmailBlock.Decisions (from the approved plan, revision 2)
layout = nullkeeps thecontentbody. Each block is one full-width table row with fixed markup.$clusterset by a subclass ornavigation.blocks.cluster).layoutreplaces the template's layout (neurobox, NB-3208).Deviations from the plan
_unsubscriberenders its own full-width<table>rather than a bare<tr>: both the package and neurobox_footerclose the outer table, so a<tr>after it would sit outside any table.widthattribute (content width - 60, or the column width) withstyle="width:100%;max-width:...px"instead ofwidth="100%": Outlook desktop ignores percentage image widths and would render the image at its natural size.(content_width - 80) / 2of content as planned, but the column wrapper (div max-width and MSO ghosttd) is that plus 20px, with 10px side padding inside each column. This gives a 20px gutter side by side and keeps the 30px edge when stacked; with the literal plan numbers the gutter would be uneven.body_colormixed at 20% overcontent_bg_colorand output as a solid hex (email clients do not reliably apply opacity to borders);#e5e7ebwhen a colour is missing.footer_color(present in neurobox themes), falling back tobody_color.renderViewPath()so screenshots of block templates show the blocks.Test plan
vendor/bin/pestin the package: 167 passed (517 assertions) after the security fix (159 passed, 479 assertions at first review), run twice in random order. New:EmailBlockRegistryTest,EmailLayoutRendererTest,EmailLayoutBuilderTest,LayoutMailableTest,EmailBlockResourceTest, plus template-form additions inResourcesTest.Manual Outlook/Gmail rendering checklist
For one template with every block type, one with a saved block, one client override with its own layout, and one campaign send, check Outlook desktop (Windows), Outlook.com, Gmail web, Gmail iOS, Apple Mail (macOS and iOS), each in light and dark mode:
Notes
5.7.0on5.x(human step; no tag created here)..php.stubmigrations are only published, not loaded by a host's migrator, so hosts need their own migrations (neurobox adds them in NB-3208; the README covers upgrades).Pre-existing observations
composer.lockis listed in.gitignorebut tracked, and is out of sync withcomposer.json(visualbuilder/filament-tinyeditorlocked at4.x-devagainst^5.0|^6.0), socomposer installfails. I rancomposer updatelocally to run the suite and did not commit the lockfile change.YouTrack
Part of NB-3148 (Part 1). Neurobox follow-up: NB-3208.
Tier: coding_complex
🤖 Generated with Claude Code