From 3844efadec239107351759607a61126392afe70c Mon Sep 17 00:00:00 2001 From: Maximilian Falco Widjaya Date: Wed, 17 Dec 2025 23:40:00 +0700 Subject: [PATCH 1/2] fix rendering callout from magic blocks --- __tests__/lib/mdxish/magic-blocks.test.ts | 20 ++++- .../transform/mdxish/mdxish-magic-blocks.ts | 79 ++++++++++++++++--- 2 files changed, 85 insertions(+), 14 deletions(-) diff --git a/__tests__/lib/mdxish/magic-blocks.test.ts b/__tests__/lib/mdxish/magic-blocks.test.ts index 30596908e..16ce6a249 100644 --- a/__tests__/lib/mdxish/magic-blocks.test.ts +++ b/__tests__/lib/mdxish/magic-blocks.test.ts @@ -144,4 +144,22 @@ ${JSON.stringify( expect((cell1.children[0] as Element).tagName).toBe('em'); }); }); -}); \ No newline at end of file + + describe('callout block', () => { + it('should restore callout block', () => { + const md = '[block:callout]{"type":"info","title":"Note","body":"This is important"}[/block]'; + + const ast = mdxish(md); + console.log(JSON.stringify(ast, null, 2)); + expect(ast.children).toHaveLength(1); + expect(ast.children[0].type).toBe('element'); + + const calloutElement = ast.children[0] as Element; + expect(calloutElement.tagName).toBe('Callout'); + expect(calloutElement.properties.type).toBe('info'); + expect(calloutElement.properties.theme).toBe('info'); + expect(calloutElement.properties.icon).toBe('📘'); + expect(calloutElement.children).toHaveLength(2); + }); + }); +}); diff --git a/processor/transform/mdxish/mdxish-magic-blocks.ts b/processor/transform/mdxish/mdxish-magic-blocks.ts index 4a28a4d6c..ab1b89fab 100644 --- a/processor/transform/mdxish/mdxish-magic-blocks.ts +++ b/processor/transform/mdxish/mdxish-magic-blocks.ts @@ -6,6 +6,7 @@ */ import type { BlockHit } from '../../../lib/utils/extractMagicBlocks'; import type { Code, Parent, Root as MdastRoot, RootContent } from 'mdast'; +import type { MdxJsxFlowElement } from 'mdast-util-mdx-jsx'; import type { Plugin } from 'unified'; import remarkGfm from 'remark-gfm'; @@ -13,6 +14,8 @@ import remarkParse from 'remark-parse'; import { unified } from 'unified'; import { visit } from 'unist-util-visit'; +import { toAttributes } from '../../utils'; + /** * Matches legacy magic block syntax: [block:TYPE]...JSON...[/block] * Group 1: block type (e.g., "image", "code", "callout") @@ -260,19 +263,38 @@ function parseMagicBlock(raw: string, options: ParseMagicBlockOptions = {}): Mda if (!(calloutJson.title || calloutJson.body)) return []; - return [ - wrapPinnedBlocks( - { - children: [...textToBlock(calloutJson.title || ''), ...textToBlock(calloutJson.body || '')], - data: { - hName: 'rdme-callout', - hProperties: { icon, theme: theme || 'default', title: calloutJson.title, value: calloutJson.body }, - }, - type: 'rdme-callout', - }, - json, - ), - ]; + // Parse title and body as markdown + const titleBlocks = textToBlock(calloutJson.title || ''); + const bodyBlocks = textToBlock(calloutJson.body || ''); + + // Convert first title block to heading (h3) if it's a paragraph, matching calloutTransformer behavior + const children: MdastNode[] = []; + if (titleBlocks.length > 0 && titleBlocks[0].type === 'paragraph') { + const firstTitle = titleBlocks[0] as { children?: MdastNode[] }; + const heading = { + type: 'heading', + depth: 3, + children: (firstTitle.children || []) as unknown[], + }; + children.push(heading as unknown as MdastNode); + children.push(...titleBlocks.slice(1), ...bodyBlocks); + } else { + children.push(...titleBlocks, ...bodyBlocks); + } + + // Create mdxJsxFlowElement directly for mdxish + const calloutElement: MdxJsxFlowElement = { + type: 'mdxJsxFlowElement', + name: 'Callout', + attributes: toAttributes({ icon, theme: theme || 'default', type: theme || 'default' }, [ + 'icon', + 'theme', + 'type', + ]), + children: children as MdxJsxFlowElement['children'], + }; + + return [wrapPinnedBlocks(calloutElement as unknown as MdastNode, json)]; } // Parameters: renders as a table (used for API parameters, etc.) @@ -388,6 +410,8 @@ const magicBlockRestorer: Plugin<[{ blocks: BlockHit[] }], MdastRoot> = const magicBlockKeys = new Map(blocks.map(({ key, raw }) => [key, raw] as const)); // Find inlineCode nodes that match our placeholder tokens + const modifications: { children: RootContent[]; index: number; parent: Parent }[] = []; + visit(tree, 'inlineCode', (node: Code, index: number, parent: Parent) => { if (!parent || index == null) return; const raw = magicBlockKeys.get(node.value); @@ -397,6 +421,35 @@ const magicBlockRestorer: Plugin<[{ blocks: BlockHit[] }], MdastRoot> = const children = parseMagicBlock(raw) as unknown as RootContent[]; if (!children.length) return; + // Check if first child is a flow element that needs unwrapping (mdxJsxFlowElement, etc.) + const needsUnwrapping = (child: RootContent): boolean => { + return child.type === 'mdxJsxFlowElement'; + }; + + if (children[0] && needsUnwrapping(children[0]) && parent.type === 'paragraph') { + // Find paragraph's parent and unwrap + let paragraphParent: Parent | undefined; + visit(tree, 'paragraph', (p, pIndex, pParent) => { + if (p === parent && pParent && 'children' in pParent) { + paragraphParent = pParent as Parent; + return false; + } + return undefined; + }); + + if (paragraphParent) { + const paragraphIndex = paragraphParent.children.indexOf(parent as RootContent); + if (paragraphIndex !== -1) { + modifications.push({ children, index: paragraphIndex, parent: paragraphParent }); + } + } + } else { + parent.children.splice(index, 1, ...children); + } + }); + + // Apply modifications in reverse order to avoid index shifting + modifications.reverse().forEach(({ children, index, parent }) => { parent.children.splice(index, 1, ...children); }); }; From 7c9adfdb9f9e0cecc8825e975714b09fcfde29d7 Mon Sep 17 00:00:00 2001 From: Maximilian Falco Widjaya Date: Wed, 17 Dec 2025 23:46:21 +0700 Subject: [PATCH 2/2] delete comments --- processor/transform/mdxish/mdxish-magic-blocks.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/processor/transform/mdxish/mdxish-magic-blocks.ts b/processor/transform/mdxish/mdxish-magic-blocks.ts index ab1b89fab..c58a9ac2a 100644 --- a/processor/transform/mdxish/mdxish-magic-blocks.ts +++ b/processor/transform/mdxish/mdxish-magic-blocks.ts @@ -263,11 +263,9 @@ function parseMagicBlock(raw: string, options: ParseMagicBlockOptions = {}): Mda if (!(calloutJson.title || calloutJson.body)) return []; - // Parse title and body as markdown const titleBlocks = textToBlock(calloutJson.title || ''); const bodyBlocks = textToBlock(calloutJson.body || ''); - // Convert first title block to heading (h3) if it's a paragraph, matching calloutTransformer behavior const children: MdastNode[] = []; if (titleBlocks.length > 0 && titleBlocks[0].type === 'paragraph') { const firstTitle = titleBlocks[0] as { children?: MdastNode[] }; @@ -417,7 +415,6 @@ const magicBlockRestorer: Plugin<[{ blocks: BlockHit[] }], MdastRoot> = const raw = magicBlockKeys.get(node.value); if (!raw) return; - // Parse the original magic block and replace the placeholder with the result const children = parseMagicBlock(raw) as unknown as RootContent[]; if (!children.length) return;