feat: first-class HTML block in the admin editor#1215
Open
scottbuscemi wants to merge 3 commits into
Open
Conversation
Add HtmlBlockNode TipTap extension to the admin editor so the existing
htmlBlock Portable Text type (produced by WordPress and Contentful
importers) is a fully editable block rather than falling through to an
opaque pluginBlock placeholder.
- New HtmlBlockNode.tsx: atom node with textarea for source editing and
a Preview toggle that sanitizes via DOMPurify
- Slash command entry (/html) and aliases (raw, markup)
- Round-trip conversion in all three converter locations:
- PortableTextEditor.tsx (admin editor)
- InlinePortableTextEditor.tsx (visual-editing editor)
- Core standalone converters (prosemirror-to-portable-text.ts,
portable-text-to-prosemirror.ts)
- New PortableTextHtmlBlock type in core converter types, exported from
the emdash package
- Inline editor renders htmlBlock as a read-only placeholder to prevent
data loss during visual editing
- Round-trip test for the core converters
Closes discussion #1185
🦋 Changeset detectedLatest commit: d698e89 The changes in this PR will be included in the next version bump. This PR includes changesets to release 14 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ✅ Deployment successful! View logs |
docs | d698e89 | May 29 2026, 07:42 PM |
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ✅ Deployment successful! View logs |
emdash-playground | d698e89 | May 29 2026, 07:43 PM |
@emdash-cms/admin
@emdash-cms/auth
@emdash-cms/blocks
@emdash-cms/cloudflare
emdash
create-emdash
@emdash-cms/gutenberg-to-portable-text
@emdash-cms/x402
@emdash-cms/plugin-ai-moderation
@emdash-cms/plugin-atproto
@emdash-cms/plugin-audit-log
@emdash-cms/plugin-color
@emdash-cms/plugin-embeds
@emdash-cms/plugin-forms
@emdash-cms/plugin-webhook-notifier
commit: |
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ✅ Deployment successful! View logs |
emdash-demo-cache | d698e89 | May 29 2026, 07:43 PM |
Drop the Eye/Preview toggle (DOMPurify-based sanitized preview) and the character count subtitle from the HTML block header. The block is now a straightforward textarea with a delete button. This also removes the dompurify import from the component.
Contributor
Overlapping PRsThis PR modifies files that are also changed by other open PRs:
This may cause merge conflicts or duplicated work. A maintainer will coordinate. |
Add HTML block documentation to the Working with Content guide: - Add /html to the slash commands table - Add HTML blocks to the editor feature list - New 'HTML Blocks' section explaining the feature and its use cases - Document the iframe sanitization allowlist limitation (youtube, vimeo only by default) - Provide a full code example for overriding the htmlBlock component to allow additional iframe providers
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.
What does this PR do?
Add a first-class
HtmlBlockNodeto the admin editor so the existinghtmlBlockPortable Text type (produced by the WordPress and Contentful importers) becomes a fully editable block. Previously, importedhtmlBlockcontent fell through to an opaquepluginBlockplaceholder with thehtmlfield becoming inaccessible. Now authors can create and edit HTML blocks natively in the rich-text editor.Closes discussion #1185
Type of change
Checklist
pnpm typecheckpassespnpm lintpassespnpm testpasses (or targeted tests for my change)pnpm formathas been runmessages.pochanges except in translation PRs — a workflow extracts catalogs on merge tomain.AI-generated code disclosure
Implementation details
New files
packages/admin/src/components/editor/HtmlBlockNode.tsx— TipTap atom node extension with React node view. Provides a textarea for editing raw HTML source, a Preview toggle that sanitizes via DOMPurify, drag handle, delete action, and selection ring. Modeled onPluginBlockNode.tsx.packages/core/tests/unit/converters/html-block-round-trip.test.ts— Round-trip test verifyinghtmlBlocksurvives PT → PM → PT conversion in the core standalone converters.Modified files
packages/admin/src/components/PortableTextEditor.tsx— AddedhtmlBlockcases toconvertPMNodeandconvertPTBlockfor round-trip conversion. Added/htmlslash command. RegisteredHtmlBlockExtensionin the extensions array.packages/core/src/components/InlinePortableTextEditor.tsx— AddedhtmlBlockcases to both converters, a read-onlyHtmlBlockNodeTipTap extension (placeholder, likePluginBlockNode),/htmlslash command, and registered the extension.packages/core/src/content/converters/types.ts— NewPortableTextHtmlBlockinterface, added to thePortableTextBlockunion.packages/core/src/content/converters/prosemirror-to-portable-text.ts— NewconvertHtmlBlockhandler in theconvertNodeswitch.packages/core/src/content/converters/portable-text-to-prosemirror.ts— NewhtmlBlockcase inconvertBlock.packages/core/src/index.ts— ExportedPortableTextHtmlBlocktype.Key design decisions
Atom node, not editable content — HTML blocks are atom nodes (like images and plugin blocks), not inline-editable text regions. The textarea is rendered via the React node view, not ProseMirror content editing.
DOMPurify for preview — The admin package already depends on
dompurify. The preview toggle runs throughDOMPurify.sanitize()so authors see what will actually render, matching the server-sidesanitizeContentin core.All user-facing strings wrapped with Lingui —
ttemplate literals for all button labels, descriptions, placeholder text, and aria attributes.RTL-safe — Uses logical properties (
start/end) throughout.Inline editor as read-only placeholder — The visual-editing (inline) editor shows HTML blocks as a simple "HTML block (edit in admin)" placeholder, preserving the data losslessly on round-trip without needing to mount the full editing UI.
Try this PR
Open a fresh playground →
A full working EmDash site, deployed from this branch. Each visit gets its own session-scoped sandbox: no login needed and no shared state. Try the admin, edit content, hit the public site.
Tracks
feat/html-block-editor. Updated automatically when the playground redeploys.