-
Notifications
You must be signed in to change notification settings - Fork 18
fix(mdxish): <HTMLBlocks> inside <Table> not rendering #1484
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weβll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
eaglethrost
merged 17 commits into
next
from
dimas/rm-16726-htmlblock-not-rendering-in-tables
Jun 4, 2026
Merged
Changes from 1 commit
Commits
Show all changes
17 commits
Select commit
Hold shift + click to select a range
6103285
fix: htmlblocks in tables by rehyping
eaglethrost 59b67fc
fix: github regex
eaglethrost 62f0f97
Merge branch 'next' into dimas/rm-16726-htmlblock-not-rendering-in-taβ¦
eaglethrost 2947839
fix: parse htmlblocks inside tables
eaglethrost 3aee631
chore: bump markdown
eaglethrost 4472bb1
test: more html blocks edge cases
eaglethrost aaef7c4
fix: use tokenizer & transformer to fix htmlbocks rendering
eaglethrost 0039d8c
chore: remove unused html block protector codes
eaglethrost a394d05
chore: simplify function & comments
eaglethrost eed8fb3
refactor: unify the htmlblock transformers
eaglethrost 3988f96
test: integrate tokenizer pr tests here, split with transformation
eaglethrost 21fcc67
chore: cleanup test
eaglethrost f0dcdf9
chore: comments
eaglethrost 2ec04fd
fix: better fallback
eaglethrost 06fdc79
Merge branch 'next' into dimas/rm-16726-htmlblock-not-rendering-in-taβ¦
eaglethrost 425c7d5
chore: comment
eaglethrost acc0fde
fix(mdxish) deindent HTMLBlock content relative to opening tag (#1501)
eaglethrost File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,123 @@ | ||
| import type { Element } from 'hast'; | ||
|
|
||
| import { mdxish } from '../../../lib'; | ||
| import { findAllElementsByTagName, findElementByTagName } from '../../helpers'; | ||
|
|
||
| describe('mdxish HTMLBlock', () => { | ||
| describe('standalone', () => { | ||
| it('renders as <html-block> with the decoded html prop', () => { | ||
| const tree = mdxish('<HTMLBlock>{`<div style="color: red;">Hello</div>`}</HTMLBlock>'); | ||
|
|
||
| const htmlBlock = findElementByTagName(tree, 'html-block'); | ||
| expect(htmlBlock).toMatchObject({ | ||
| type: 'element', | ||
| tagName: 'html-block', | ||
| properties: { html: '<div style="color: red;">Hello</div>' }, | ||
| children: [], | ||
| }); | ||
| }); | ||
| }); | ||
|
|
||
| describe('nested inside JSX blocks (RM-16726)', () => { | ||
| it('renders inside a <Table> cell as <html-block> with the decoded html prop', () => { | ||
| const md = `<Table> | ||
| <thead> | ||
| <tr><th>Name</th><th>Markup</th></tr> | ||
| </thead> | ||
| <tbody> | ||
| <tr> | ||
| <td>Custom</td> | ||
| <td><HTMLBlock>{\`<div style="color: red;">Hello</div>\`}</HTMLBlock></td> | ||
| </tr> | ||
| </tbody> | ||
| </Table>`; | ||
|
|
||
| const tree = mdxish(md); | ||
|
|
||
| const rawHtmlBlock = findElementByTagName(tree, 'HTMLBlock'); | ||
| expect(rawHtmlBlock).toBeNull(); | ||
|
|
||
| const htmlBlock = findElementByTagName(tree, 'html-block'); | ||
| expect(htmlBlock).toMatchObject({ | ||
| type: 'element', | ||
| tagName: 'html-block', | ||
| properties: { html: '<div style="color: red;">Hello</div>' }, | ||
| children: [], | ||
| }); | ||
| }); | ||
|
|
||
| it('renders inside a generic JSX block as <html-block> with the decoded html prop', () => { | ||
| const md = '<div><HTMLBlock>{`<p>nested</p>`}</HTMLBlock></div>'; | ||
|
|
||
| const tree = mdxish(md); | ||
|
|
||
| const htmlBlock = findElementByTagName(tree, 'html-block'); | ||
| expect(htmlBlock).toMatchObject({ | ||
| type: 'element', | ||
| tagName: 'html-block', | ||
| properties: { html: '<p>nested</p>' }, | ||
| }); | ||
| }); | ||
|
|
||
| it('preserves safeMode and runScripts attributes when nested', () => { | ||
| const md = `<Table> | ||
| <tbody> | ||
| <tr> | ||
| <td><HTMLBlock safeMode="true" runScripts="false">{\`<div>raw</div>\`}</HTMLBlock></td> | ||
| </tr> | ||
| </tbody> | ||
| </Table>`; | ||
|
|
||
| const tree = mdxish(md); | ||
|
|
||
| const htmlBlock = findElementByTagName(tree, 'html-block'); | ||
| expect(htmlBlock).toMatchObject({ | ||
| type: 'element', | ||
| tagName: 'html-block', | ||
| properties: { | ||
| html: '<div>raw</div>', | ||
| safeMode: 'true', | ||
| runScripts: 'false', | ||
| }, | ||
| }); | ||
| }); | ||
|
|
||
| it('renders multiple HTMLBlocks inside the same Table', () => { | ||
| const md = `<Table> | ||
| <tbody> | ||
| <tr> | ||
| <td><HTMLBlock>{\`<span>one</span>\`}</HTMLBlock></td> | ||
| <td><HTMLBlock>{\`<span>two</span>\`}</HTMLBlock></td> | ||
| </tr> | ||
| </tbody> | ||
| </Table>`; | ||
|
|
||
| const tree = mdxish(md); | ||
|
|
||
| const htmlBlocks = findAllElementsByTagName(tree, 'html-block'); | ||
| expect(htmlBlocks).toHaveLength(2); | ||
| expect(htmlBlocks[0].properties).toMatchObject({ html: '<span>one</span>' }); | ||
| expect(htmlBlocks[1].properties).toMatchObject({ html: '<span>two</span>' }); | ||
| }); | ||
|
|
||
| it('leaves no RDMX_HTMLBLOCK markers or stray comment nodes in the tree', () => { | ||
| const md = `<Table> | ||
| <tbody> | ||
| <tr> | ||
| <td> | ||
| <HTMLBlock>{\`<div>x</div>\`}</HTMLBlock> | ||
| </td> | ||
| </tr> | ||
| </tbody> | ||
| </Table>`; | ||
|
|
||
| const tree = mdxish(md); | ||
| const serialized = JSON.stringify(tree); | ||
|
|
||
| expect(serialized).not.toContain('RDMX_HTMLBLOCK'); | ||
|
|
||
| const htmlBlock = findElementByTagName(tree, 'html-block') as Element; | ||
| expect(htmlBlock.children).toStrictEqual([]); | ||
| }); | ||
| }); | ||
| }); |
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
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,74 @@ | ||
| import type { Element, ElementContent, Properties, Root } from 'hast'; | ||
| import type { Transformer } from 'unified'; | ||
|
|
||
| import { visit } from 'unist-util-visit'; | ||
|
|
||
| import { formatHtmlForMdxish } from '../../utils'; | ||
|
|
||
| import { base64Decode, HTML_BLOCK_CONTENT_END, HTML_BLOCK_CONTENT_START } from './preprocess-jsx-expressions'; | ||
|
|
||
| const startEscaped = HTML_BLOCK_CONTENT_START.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); | ||
| const endEscaped = HTML_BLOCK_CONTENT_END.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); | ||
| // Marker emitted as an HTML comment by preprocessJSXExpressions. rehypeRaw parses | ||
| // the comment body (sans <!-- -->), so we match the inner form here. | ||
| const COMMENT_MARKER_RE = new RegExp( | ||
| `^${startEscaped.replace(/^<!--/, '')}([A-Za-z0-9+/=]+)${endEscaped.replace(/-->$/, '')}$`, | ||
| ); | ||
|
|
||
| const KNOWN_HTML_BLOCK_PROPS: Record<string, string> = { | ||
| safemode: 'safeMode', | ||
| runscripts: 'runScripts', | ||
| }; | ||
|
|
||
| function decodeProtectedComment(value: string): string | null { | ||
| const match = value.match(COMMENT_MARKER_RE); | ||
| if (!match) return null; | ||
| try { | ||
| return base64Decode(match[1]); | ||
| } catch { | ||
| return null; | ||
| } | ||
| } | ||
|
|
||
| function findEncodedPayload(children: ElementContent[]): string | null { | ||
| return children.reduce<string | null>((found, child) => { | ||
| if (found !== null) return found; | ||
| if (child.type !== 'comment') return null; | ||
| return decodeProtectedComment(child.value); | ||
| }, null); | ||
| } | ||
|
|
||
| function normalizeHtmlBlockProperties(properties: Properties | undefined, html: string): Properties { | ||
| const normalized: Properties = { html }; | ||
| if (!properties) return normalized; | ||
|
|
||
| Object.entries(properties).forEach(([key, value]) => { | ||
| if (key === 'html') return; | ||
| const canonical = KNOWN_HTML_BLOCK_PROPS[key.toLowerCase()] ?? key; | ||
| normalized[canonical] = value; | ||
| }); | ||
| return normalized; | ||
| } | ||
|
|
||
| /** | ||
| * Converts <HTMLBlock> elements that survived rehypeRaw (because they were nested | ||
| * inside another JSX block like <Table>, so the mdast-level transformer never saw | ||
| * them) into the canonical <html-block> hast element the renderer expects. | ||
| */ | ||
| const rehypeHtmlBlocksInJsx = (): Transformer<Root, Root> => tree => { | ||
| visit(tree, 'element', (node: Element) => { | ||
| // rehypeRaw routes HTMLBlock through parse5, which lowercases tag names. | ||
| if (node.tagName.toLowerCase() !== 'htmlblock') return; | ||
|
|
||
| const encoded = findEncodedPayload(node.children ?? []); | ||
| if (encoded === null) return; | ||
|
|
||
| const html = formatHtmlForMdxish(encoded); | ||
|
|
||
| node.tagName = 'html-block'; | ||
| node.properties = normalizeHtmlBlockProperties(node.properties, html); | ||
| node.children = []; | ||
| }); | ||
| }; | ||
|
|
||
| export default rehypeHtmlBlocksInJsx; | ||
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.
Uh oh!
There was an error while loading. Please reload this page.