Skip to content

Commit 2ee53b1

Browse files
committed
Merge branch 'next'
2 parents 8325458 + 94c3753 commit 2ee53b1

File tree

2 files changed

+118
-0
lines changed

2 files changed

+118
-0
lines changed

__tests__/lib/mdxish/magic-blocks.test.ts

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,88 @@ ${JSON.stringify(
143143
expect((cell0.children[0] as Element).tagName).toBe('strong');
144144
expect((cell1.children[0] as Element).tagName).toBe('em');
145145
});
146+
147+
it('should preserve multiple paragraphs with links in table cells', () => {
148+
const md = `
149+
[block:parameters]
150+
${JSON.stringify(
151+
{
152+
data: {
153+
'h-0': 'Feature',
154+
'h-1': 'Description',
155+
'0-0':
156+
'**Webhooks** \nProfile activity event delivery service to your listener endpoint. Configured via API: you provide the endpoint and then consume webhook notifications. \n \n[Introduction to Webhooks v3](doc:an-introduction-to-webhooks-v3-1)',
157+
'0-1': 'Available',
158+
},
159+
cols: 2,
160+
rows: 1,
161+
align: ['left', 'left'],
162+
},
163+
null,
164+
2,
165+
)}
166+
[/block]`;
167+
168+
const ast = mdxish(md);
169+
expect(ast.children).toHaveLength(4);
170+
171+
// Table is the 3rd child
172+
const element = ast.children[2] as Element;
173+
expect(element.tagName).toBe('table');
174+
175+
const tbody = element.children[1] as Element;
176+
const row = tbody.children[0] as Element;
177+
const cell = row.children[0] as Element;
178+
179+
expect(cell.children.length).toBeGreaterThan(1);
180+
expect(cell.children[0].type).toBe('element');
181+
expect((cell.children[0] as Element).tagName).toBe('p');
182+
183+
const lastParagraph = cell.children[cell.children.length - 1] as Element;
184+
expect(lastParagraph.tagName).toBe('p');
185+
expect((lastParagraph.children[0] as Element).tagName).toBe('a');
186+
expect((lastParagraph.children[0] as Element).properties.href).toBe('doc:an-introduction-to-webhooks-v3-1');
187+
});
188+
});
189+
190+
describe('recipe block', () => {
191+
it('should restore tutorial-tile block to Recipe component', () => {
192+
const md = `[block:tutorial-tile]
193+
{
194+
"emoji": "🦉",
195+
"slug": "whoaaa",
196+
"title": "WHOAAA"
197+
}
198+
[/block]`;
199+
200+
const ast = mdxish(md);
201+
expect(ast.children).toHaveLength(1);
202+
expect(ast.children[0].type).toBe('element');
203+
204+
const recipeElement = ast.children[0] as Element;
205+
expect(recipeElement.tagName).toBe('Recipe');
206+
expect(recipeElement.properties.slug).toBe('whoaaa');
207+
expect(recipeElement.properties.title).toBe('WHOAAA');
208+
});
209+
210+
it('should restore recipe block to Recipe component', () => {
211+
const md = `[block:recipe]
212+
{
213+
"slug": "test-recipe",
214+
"title": "Test Recipe",
215+
"emoji": "👉"
216+
}
217+
[/block]`;
218+
219+
const ast = mdxish(md);
220+
expect(ast.children).toHaveLength(1);
221+
expect(ast.children[0].type).toBe('element');
222+
223+
const recipeElement = ast.children[0] as Element;
224+
expect(recipeElement.tagName).toBe('Recipe');
225+
expect(recipeElement.properties.slug).toBe('test-recipe');
226+
expect(recipeElement.properties.title).toBe('Test Recipe');
227+
});
146228
});
147229

148230
describe('callout block', () => {

processor/transform/mdxish/mdxish-magic-blocks.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,15 @@ interface HtmlJson extends MagicBlockJson {
7979
html: string;
8080
}
8181

82+
interface RecipeJson extends MagicBlockJson {
83+
backgroundColor?: string;
84+
emoji?: string;
85+
id?: string;
86+
link?: string;
87+
slug: string;
88+
title: string;
89+
}
90+
8291
export interface ParseMagicBlockOptions {
8392
alwaysThrow?: boolean;
8493
compatibilityMode?: boolean;
@@ -129,6 +138,12 @@ const textToBlock = (text: string): MdastNode[] => [{ children: textToInline(tex
129138
const parseInline = (text: string): MdastNode[] => {
130139
if (!text.trim()) return [{ type: 'text', value: '' }];
131140
const tree = cellParser.runSync(cellParser.parse(text)) as MdastRoot;
141+
142+
// If there are multiple block-level nodes, keep them as-is to preserve the document structure and spacing
143+
if (tree.children.length > 1) {
144+
return tree.children as MdastNode[];
145+
}
146+
132147
return tree.children.flatMap(n =>
133148
// This unwraps the extra p node that might appear & wrapping the content
134149
n.type === 'paragraph' && 'children' in n ? (n.children as MdastNode[]) : [n as MdastNode],
@@ -379,6 +394,27 @@ function parseMagicBlock(raw: string, options: ParseMagicBlockOptions = {}): Mda
379394
];
380395
}
381396

397+
// Recipe/TutorialTile: renders as Recipe component
398+
case 'recipe':
399+
case 'tutorial-tile': {
400+
const recipeJson = json as RecipeJson;
401+
if (!recipeJson.slug || !recipeJson.title) return [];
402+
403+
// Create mdxJsxFlowElement directly for mdxish flow
404+
// Note: Don't wrap in pinned blocks for mdxish - rehypeMdxishComponents handles component resolution
405+
// The node structure matches what mdxishComponentBlocks creates for JSX tags
406+
const recipeNode: MdxJsxFlowElement = {
407+
type: 'mdxJsxFlowElement',
408+
name: 'Recipe',
409+
attributes: toAttributes(recipeJson, ['slug', 'title']),
410+
children: [],
411+
// Position is optional but helps with debugging
412+
position: undefined,
413+
};
414+
415+
return [recipeNode as unknown as MdastNode];
416+
}
417+
382418
// Unknown block types: render as generic div with JSON properties
383419
default: {
384420
const text = (json as { html?: string; text?: string }).text || (json as { html?: string }).html || '';

0 commit comments

Comments
 (0)